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 ur.row(needApproveCW,UserRole.APPROVE_UR);
446 } catch (OrganizationException e) {
447 noAvg.error().log(e);
456 Now Process Owners, one owner Role at a time, ensuring one is left,
457 preferably a good one. If so, process the others as normal.
459 Otherwise, write to ExpiredOwners Report
461 tt = trans.start("Analyze Owners Separately",Trans.SUB);
463 if (!owners.values().isEmpty()) {
464 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
465 final CSV ownerCSV = new CSV(env.access(),file);
466 CSV.Writer expOwner = ownerCSV.writer();
467 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
470 for (Set<UserRole> sur : owners.values()) {
472 for (UserRole ur : sur) {
473 if (ur.expires().after(now)) {
478 for (UserRole ur : sur) {
479 if (goodOwners >= minOwners) {
480 Range r = writeAnalysis(noAvg, ur);
482 Approval existing = findApproval(ur);
484 ur.row(needApproveCW,UserRole.APPROVE_UR);
488 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
489 Approval existing = findApproval(ur);
491 ur.row(needApproveCW,UserRole.APPROVE_UR);
511 * Check for Expired Credentials
514 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
515 Cred.load(trans, session);
517 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
519 for (Cred cred : Cred.data.values()) {
520 List<Instance> linst = cred.instances;
522 Instance lastBath = null;
523 for(Instance inst : linst) {
524 // All Creds go through Life Cycle
525 if(deleteDate!=null && inst.expires.before(deleteDate)) {
526 writeAnalysis(noAvg, cred, inst); // will go to Delete
527 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
528 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
529 if(lastBath==null || lastBath.expires.before(inst.expires)) {
535 writeAnalysis(noAvg, cred, lastBath);
547 tt = trans.start("Analyze Expired X509s",Trans.SUB);
549 X509.load(noAvg, session, x509 -> {
551 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
552 writeAnalysis(noAvg, x509, (X509Certificate)cert);
554 } catch (CertificateException | IOException e) {
555 noAvg.error().log(e, "Error Decrypting X509");
561 } catch (FileNotFoundException e) {
566 private Approval findApproval(UserRole ur) {
567 Approval existing = null;
568 List<Approval> apprs = Approval.byUser.get(ur.user());
570 for(Approval appr : apprs) {
571 if(ur.role().equals(appr.getRole()) &&
572 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
580 private Range writeAnalysis(AuthzTrans noAvg, UserRole ur) {
581 Range r = expireRange.getRange("ur", ur.expires());
583 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
584 // Note: lnd is NEVER null
587 i = org.getIdentity(noAvg, ur.user());
588 } catch (OrganizationException e) {
591 if(r.needsContact(lnd,i)) {
592 CSV.Writer cw = writerList.get(r.name());
594 ur.row(cw,UserRole.UR);
601 private void writeAnalysis(AuthzTrans noAvg, Cred cred, Instance inst) {
602 if(cred!=null && inst!=null) {
603 Range r = expireRange.getRange("cred", inst.expires);
605 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
606 // Note: lnd is NEVER null
609 i = org.getIdentity(noAvg, cred.id);
610 } catch (OrganizationException e) {
613 if(r.needsContact(lnd,i)) {
614 CSV.Writer cw = writerList.get(r.name());
623 private void writeAnalysis(AuthzTrans noAvg, X509 x509, X509Certificate x509Cert) throws IOException {
624 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
626 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
627 // Note: lnd is NEVER null
630 i = org.getIdentity(noAvg, x509.id);
631 } catch (OrganizationException e) {
634 if(r.needsContact(lnd,i)) {
635 CSV.Writer cw = writerList.get(r.name());
637 x509.row(cw,x509Cert);
644 protected void _close(AuthzTrans trans) {
646 for(CSV.Writer cw : writerList.values()) {