2 * ============LICENSE_START====================================================
4 * ===========================================================================
5 * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
7 * Modifications Copyright (C) 2019 IBM.
8 * ===========================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * ============LICENSE_END====================================================
24 package org.onap.aaf.auth.batch.reports;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.security.cert.Certificate;
30 import java.security.cert.CertificateException;
31 import java.security.cert.X509Certificate;
32 import java.util.Date;
33 import java.util.GregorianCalendar;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.List;
38 import java.util.Map.Entry;
40 import java.util.TreeMap;
41 import java.util.TreeSet;
42 import java.util.UUID;
44 import org.onap.aaf.auth.batch.Batch;
45 import org.onap.aaf.auth.batch.approvalsets.Pending;
46 import org.onap.aaf.auth.batch.approvalsets.Ticket;
47 import org.onap.aaf.auth.batch.helpers.Approval;
48 import org.onap.aaf.auth.batch.helpers.Cred;
49 import org.onap.aaf.auth.batch.helpers.Cred.Instance;
50 import org.onap.aaf.auth.batch.helpers.ExpireRange;
51 import org.onap.aaf.auth.batch.helpers.ExpireRange.Range;
52 import org.onap.aaf.auth.batch.helpers.Future;
53 import org.onap.aaf.auth.batch.helpers.LastNotified;
54 import org.onap.aaf.auth.batch.helpers.Role;
55 import org.onap.aaf.auth.batch.helpers.UserRole;
56 import org.onap.aaf.auth.batch.helpers.X509;
57 import org.onap.aaf.auth.dao.cass.CredDAO;
58 import org.onap.aaf.auth.dao.cass.UserRoleDAO;
59 import org.onap.aaf.auth.env.AuthzTrans;
60 import org.onap.aaf.auth.org.Organization.Expiration;
61 import org.onap.aaf.auth.org.Organization.Identity;
62 import org.onap.aaf.auth.org.OrganizationException;
63 import org.onap.aaf.cadi.configure.Factory;
64 import org.onap.aaf.cadi.util.CSV;
65 import org.onap.aaf.misc.env.APIException;
66 import org.onap.aaf.misc.env.Env;
67 import org.onap.aaf.misc.env.TimeTaken;
68 import org.onap.aaf.misc.env.Trans;
69 import org.onap.aaf.misc.env.util.Chrono;
72 public class Analyze extends Batch {
73 private static final int unknown=0;
74 private static final int owner=1;
75 private static final int supervisor=2;
76 private static final int total=0;
77 private static final int pending=1;
78 private static final int approved=2;
81 public static final String NEED_APPROVALS = "NeedApprovals";
82 private static final String EXTEND = "Extend";
83 private static final String EXPIRED_OWNERS = "ExpiredOwners";
84 private static final String CSV = ".csv";
85 private static final String INFO = "info";
86 private static final String NOT_COMPLIANT = "NotCompliant";
87 private int minOwners;
88 private Map<String, CSV.Writer> writerList;
89 private ExpireRange expireRange;
90 private Date deleteDate;
91 private CSV.Writer deleteCW;
92 private CSV.Writer needApproveCW;
93 private CSV.Writer extendCW;
94 private CSV.Writer notCompliantCW;
95 private Range futureRange;
96 private final String sdate;
97 private LastNotified ln;
99 public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
101 trans.info().log("Starting Connection Process");
103 TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
105 TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
107 session = cluster.connect();
115 // Create Intermediate Output
116 writerList = new HashMap<>();
118 expireRange = new ExpireRange(trans.env().access());
119 sdate = Chrono.dateOnlyStamp(now);
120 for( List<Range> lr : expireRange.ranges.values()) {
122 if(writerList.get(r.name())==null) {
123 File file = new File(logDir(),r.name() + sdate +CSV);
124 CSV csv = new CSV(env.access(),file);
125 CSV.Writer cw = csv.writer(false);
126 cw.row(INFO,r.name(),sdate,r.reportingLevel());
127 writerList.put(r.name(),cw);
128 if("Delete".equals(r.name())) {
129 deleteDate = r.getEnd();
132 trans.init().log("Creating File:",file.getAbsolutePath());
137 // Setup New Approvals file
138 futureRange = expireRange.newFutureRange();
139 File file = new File(logDir(),NEED_APPROVALS + sdate +CSV);
140 CSV approveCSV = new CSV(env.access(),file);
141 needApproveCW = approveCSV.writer();
142 needApproveCW.row(INFO,NEED_APPROVALS,sdate,1);
143 writerList.put(NEED_APPROVALS,needApproveCW);
145 // Setup Extend Approvals file
146 file = new File(logDir(),EXTEND + sdate +CSV);
147 CSV extendCSV = new CSV(env.access(),file);
148 extendCW = extendCSV.writer();
149 extendCW.row(INFO,EXTEND,sdate,1);
150 writerList.put(EXTEND,extendCW);
152 // Setup NotCompliant Writer for Apps
153 file = new File(logDir(),NOT_COMPLIANT + sdate + CSV);
154 CSV ncCSV = new CSV(env.access(),file);
155 notCompliantCW = ncCSV.writer();
156 writerList.put(NOT_COMPLIANT, notCompliantCW);
158 // Load full data of the following
159 ln = new LastNotified(session);
167 protected void run(AuthzTrans trans) {
169 AuthzTrans noAvg = trans.env().newTransNoAvg();
172 // Load all Notifieds, and either add to local Data, or mark for Deletion.
173 ln.loadAll(noAvg,expireRange.approveDelete,deleteCW);
175 // Hold Good Tickets to keyed User/Role for UserRole Step
176 Map<String,Ticket> mur = new TreeMap<>();
179 Approval.load(trans, session, Approval.v2_0_17);
182 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
183 tt = trans.start("Analyze Expired Futures",Trans.SUB);
185 Future.load(noAvg, session, Future.withConstruct, fut -> {
186 List<Approval> appls = Approval.byTicket.get(fut.id());
187 if(!futureRange.inRange(fut.expires())) {
188 deleteCW.comment("Future %s expired", fut.id());
189 Future.row(deleteCW,fut);
191 for(Approval a : appls) {
192 Approval.row(deleteCW, a);
195 } else if(appls==null) { // Orphaned Future (no Approvals)
196 deleteCW.comment("Future is Orphaned");
197 Future.row(deleteCW,fut);
199 goodTickets.put(fut.fdd.id, new Ticket(fut));
206 Set<String> approvers = new TreeSet<>();
207 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
209 for(Approval appr : Approval.list) {
211 UUID ticketID = appr.getTicket();
213 ticket = goodTickets.get(appr.getTicket());
215 if(ticket == null) { // Orphaned Approvals, no Futures
216 deleteCW.comment("Approval is Orphaned");
217 Approval.row(deleteCW, appr);
219 // for users and approvers still valid
220 String user = appr.getUser();
222 Date revokedAppr = org.isRevoked(noAvg, appr.getApprover());
223 Date revokedUser = org.isRevoked(noAvg, user);
224 if(revokedAppr!=null) {
225 deleteCW.comment("Approver ID is revoked on " + revokedAppr);
226 Approval.row(deleteCW, appr);
227 } else if(user!=null && !user.isEmpty() && revokedUser!=null) {
228 deleteCW.comment("USER ID is revoked on " + revokedUser);
229 Approval.row(deleteCW, appr);
231 ticket.approvals.add(appr); // add to found Ticket
232 approvers.add(appr.getApprover());
240 /* Run through all Futures, and see if
241 * 1) they have been executed (no longer valid)
242 * 2) The current Approvals indicate they can proceed
244 Map<String,Pending> pendingApprs = new HashMap<>();
245 Map<String,Pending> pendingTemp = new HashMap<>();
249 tt = trans.start("Analyze Good Tickets",Trans.SUB);
251 for(Ticket ticket : goodTickets.values()) {
254 switch(ticket.f.target()) {
256 int state[][] = new int[3][3];
259 for(Approval appr : ticket.approvals) {
260 switch(appr.getType()) {
270 ++state[type][total]; // count per type
271 switch(appr.getStatus()) {
273 ++state[type][pending];
274 approver = appr.getApprover();
275 Pending n = pendingTemp.get(approver);
277 Date lastNotified = ln.lastNotified(approver,"pending",null);
278 pendingTemp.put(approver,new Pending(lastNotified));
284 ++state[type][approved];
287 ++state[type][unknown];
292 // Always must have at least 1 owner
293 if((state[owner][total]>0 && state[owner][approved]>0) &&
294 // If there are no Supervisors, that's ok
295 (state[supervisor][total]==0 ||
296 // But if there is a Supervisor, they must have approved
297 (state[supervisor][approved]>0))) {
298 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
300 urdd.reconstitute(ticket.f.fdd.construct);
301 if(urdd.expires.before(ticket.f.expires())) {
302 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
304 } catch (IOException e) {
305 trans.error().log("Could not reconstitute UserRole");
307 } else { // Load all the Pending.
308 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
309 Pending p = pendingApprs.get(es.getKey());
311 pendingApprs.put(es.getKey(), es.getValue());
313 p.inc(es.getValue());
320 if("user_role".equals(ticket.f.fdd.target)) {
321 String key = ticket.f.fdd.target_key;
323 mur.put(key, ticket);
331 // Good Tickets no longer needed
335 * Decide to Notify about Approvals, based on activity/last Notified
337 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
339 GregorianCalendar gc = new GregorianCalendar();
340 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
341 Date remind = gc.getTime();
343 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
344 Pending p = es.getValue();
346 || p.earliest() == LastNotified.NEVER // yes, equals.
347 || p.earliest().after(remind)) {
348 p.row(needApproveCW,es.getKey());
355 // clear out Approval Intermediates
362 Run through User Roles.
363 Owners are treated specially in next section.
364 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
368 Role.load(trans, session);
371 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
372 Set<String> specialCommented = new HashSet<>();
373 Map<String, Set<UserRole>> owners = new TreeMap<>();
375 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
378 identity = trans.org().getIdentity(noAvg,ur.user());
380 // Candidate for Delete, but not Users if Special
381 String id = ur.user();
382 for(String s : specialDomains) {
384 if(!specialCommented.contains(id)) {
385 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
386 specialCommented.add(id);
391 if(specialNames.contains(id)) {
392 if(!specialCommented.contains(id)) {
393 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
394 specialCommented.add(id);
398 Date revoked = org.isRevoked(trans, ur.user());
400 GregorianCalendar gc = new GregorianCalendar();
402 GregorianCalendar gracePeriodEnds = org.expiration(gc, Expiration.RevokedGracePeriodEnds, ur.user());
403 if(now.after(gracePeriodEnds.getTime())) {
404 ur.row(deleteCW, UserRole.UR,"Revoked ID, no grace period left");
406 ur.row(notCompliantCW, UserRole.UR, "Revoked ID: WARNING! GracePeriod Ends " + Chrono.dateOnlyStamp(gracePeriodEnds));
410 ur.row(deleteCW, UserRole.UR,"Not in Organization");
412 } else if(Role.byName.get(ur.role())==null) {
413 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
415 // Make sure owners can still be owners.
416 } else if(ur.role().endsWith(".owner")) {
417 String err = identity.mayOwn();
419 ur.row(deleteCW, UserRole.UR,String.format("%s may not be an owner: %s",ur.user(),err));
426 // Just let expired UserRoles sit until deleted
427 if(futureRange.inRange(ur.expires())&&(!mur.containsKey(ur.user() + '|' + ur.role()))) {
428 // Cannot just delete owners, unless there is at least one left. Process later
429 if ("owner".equals(ur.rname())) {
430 Set<UserRole> urs = owners.get(ur.role());
432 urs = new HashSet<UserRole>();
433 owners.put(ur.role(), urs);
437 Range r = writeAnalysis(noAvg,ur);
439 Approval existing = findApproval(ur);
441 if (org.isUserExpireExempt(ur.user(), ur.expires())) {
442 ur.row(notCompliantCW, UserRole.UR);
444 ur.row(needApproveCW, UserRole.APPROVE_UR,
445 "Expired user role! Membership expired " + Chrono.dateOnlyStamp(ur.expires()));
451 } catch (OrganizationException e) {
452 noAvg.error().log(e);
461 Now Process Owners, one owner Role at a time, ensuring one is left,
462 preferably a good one. If so, process the others as normal.
464 Otherwise, write to ExpiredOwners Report
466 tt = trans.start("Analyze Owners Separately",Trans.SUB);
468 if (!owners.values().isEmpty()) {
469 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
470 final CSV ownerCSV = new CSV(env.access(),file);
471 CSV.Writer expOwner = ownerCSV.writer();
472 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
475 for (Set<UserRole> sur : owners.values()) {
477 for (UserRole ur : sur) {
478 if (ur.expires().after(now)) {
483 for (UserRole ur : sur) {
484 if (goodOwners >= minOwners) {
485 Range r = writeAnalysis(noAvg, ur);
487 Approval existing = findApproval(ur);
489 ur.row(needApproveCW,UserRole.APPROVE_UR);
493 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
494 Approval existing = findApproval(ur);
496 ur.row(needApproveCW,UserRole.APPROVE_UR);
516 * Check for Expired Credentials
519 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
520 Cred.load(trans, session);
522 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
524 for (Cred cred : Cred.data.values()) {
525 List<Instance> linst = cred.instances;
527 Instance lastBath = null;
528 for(Instance inst : linst) {
529 // All Creds go through Life Cycle
530 if(deleteDate!=null && inst.expires.before(deleteDate)) {
531 writeAnalysis(noAvg, cred, inst); // will go to Delete
532 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
533 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
534 if(lastBath==null || lastBath.expires.before(inst.expires)) {
540 writeAnalysis(noAvg, cred, lastBath);
552 tt = trans.start("Analyze Expired X509s",Trans.SUB);
554 X509.load(noAvg, session, x509 -> {
556 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
557 writeAnalysis(noAvg, x509, (X509Certificate)cert);
559 } catch (CertificateException | IOException e) {
560 noAvg.error().log(e, "Error Decrypting X509");
566 } catch (FileNotFoundException e) {
571 private Approval findApproval(UserRole ur) {
572 Approval existing = null;
573 List<Approval> apprs = Approval.byUser.get(ur.user());
575 for(Approval appr : apprs) {
576 if(ur.role().equals(appr.getRole()) &&
577 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
585 private Range writeAnalysis(AuthzTrans noAvg, UserRole ur) {
586 Range r = expireRange.getRange("ur", ur.expires());
588 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
589 // Note: lnd is NEVER null
592 i = org.getIdentity(noAvg, ur.user());
593 } catch (OrganizationException e) {
596 if(r.needsContact(lnd,i)) {
597 CSV.Writer cw = writerList.get(r.name());
599 ur.row(cw,UserRole.UR);
606 private void writeAnalysis(AuthzTrans noAvg, Cred cred, Instance inst) {
607 if(cred!=null && inst!=null) {
608 Range r = expireRange.getRange("cred", inst.expires);
610 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
611 // Note: lnd is NEVER null
614 i = org.getIdentity(noAvg, cred.id);
615 } catch (OrganizationException e) {
618 if(r.needsContact(lnd,i)) {
619 CSV.Writer cw = writerList.get(r.name());
628 private void writeAnalysis(AuthzTrans noAvg, X509 x509, X509Certificate x509Cert) throws IOException {
629 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
631 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
632 // Note: lnd is NEVER null
635 i = org.getIdentity(noAvg, x509.id);
636 } catch (OrganizationException e) {
639 if(r.needsContact(lnd,i)) {
640 CSV.Writer cw = writerList.get(r.name());
642 x509.row(cw,x509Cert);
649 protected void _close(AuthzTrans trans) {
651 for(CSV.Writer cw : writerList.values()) {