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.Identity;
61 import org.onap.aaf.auth.org.OrganizationException;
62 import org.onap.aaf.cadi.configure.Factory;
63 import org.onap.aaf.cadi.util.CSV;
64 import org.onap.aaf.misc.env.APIException;
65 import org.onap.aaf.misc.env.Env;
66 import org.onap.aaf.misc.env.TimeTaken;
67 import org.onap.aaf.misc.env.Trans;
68 import org.onap.aaf.misc.env.util.Chrono;
71 public class Analyze extends Batch {
72 private static final int unknown=0;
73 private static final int owner=1;
74 private static final int supervisor=2;
75 private static final int total=0;
76 private static final int pending=1;
77 private static final int approved=2;
80 public static final String NEED_APPROVALS = "NeedApprovals";
81 private static final String EXTEND = "Extend";
82 private static final String EXPIRED_OWNERS = "ExpiredOwners";
83 private static final String CSV = ".csv";
84 private static final String INFO = "info";
85 private int minOwners;
86 private Map<String, CSV.Writer> writerList;
87 private ExpireRange expireRange;
88 private Date deleteDate;
89 private CSV.Writer deleteCW;
90 private CSV.Writer needApproveCW;
91 private CSV.Writer extendCW;
92 private Range futureRange;
93 private final String sdate;
94 private LastNotified ln;
96 public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
98 trans.info().log("Starting Connection Process");
100 TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
102 TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
104 session = cluster.connect();
112 // Create Intermediate Output
113 writerList = new HashMap<>();
115 expireRange = new ExpireRange(trans.env().access());
116 sdate = Chrono.dateOnlyStamp(now);
117 for( List<Range> lr : expireRange.ranges.values()) {
119 if(writerList.get(r.name())==null) {
120 File file = new File(logDir(),r.name() + sdate +CSV);
121 CSV csv = new CSV(env.access(),file);
122 CSV.Writer cw = csv.writer(false);
123 cw.row(INFO,r.name(),sdate,r.reportingLevel());
124 writerList.put(r.name(),cw);
125 if("Delete".equals(r.name())) {
126 deleteDate = r.getEnd();
129 trans.init().log("Creating File:",file.getAbsolutePath());
134 // Setup New Approvals file
135 futureRange = expireRange.newFutureRange();
136 File file = new File(logDir(),NEED_APPROVALS + sdate +CSV);
137 CSV approveCSV = new CSV(env.access(),file);
138 needApproveCW = approveCSV.writer();
139 needApproveCW.row(INFO,NEED_APPROVALS,sdate,1);
140 writerList.put(NEED_APPROVALS,needApproveCW);
142 // Setup Extend Approvals file
143 file = new File(logDir(),EXTEND + sdate +CSV);
144 CSV extendCSV = new CSV(env.access(),file);
145 extendCW = extendCSV.writer();
146 extendCW.row(INFO,EXTEND,sdate,1);
147 writerList.put(EXTEND,extendCW);
149 // Load full data of the following
150 ln = new LastNotified(session);
158 protected void run(AuthzTrans trans) {
160 AuthzTrans noAvg = trans.env().newTransNoAvg();
163 // Load all Notifieds, and either add to local Data, or mark for Deletion.
164 ln.loadAll(noAvg,expireRange.approveDelete,deleteCW);
166 // Hold Good Tickets to keyed User/Role for UserRole Step
167 Map<String,Ticket> mur = new TreeMap<>();
170 Approval.load(trans, session, Approval.v2_0_17);
173 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
174 tt = trans.start("Analyze Expired Futures",Trans.SUB);
176 Future.load(noAvg, session, Future.withConstruct, fut -> {
177 List<Approval> appls = Approval.byTicket.get(fut.id());
178 if(!futureRange.inRange(fut.expires())) {
179 deleteCW.comment("Future %s expired", fut.id());
180 Future.row(deleteCW,fut);
182 for(Approval a : appls) {
183 Approval.row(deleteCW, a);
186 } else if(appls==null) { // Orphaned Future (no Approvals)
187 deleteCW.comment("Future is Orphaned");
188 Future.row(deleteCW,fut);
190 goodTickets.put(fut.fdd.id, new Ticket(fut));
197 Set<String> approvers = new TreeSet<>();
198 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
200 for(Approval appr : Approval.list) {
202 UUID ticketID = appr.getTicket();
204 ticket = goodTickets.get(appr.getTicket());
206 if(ticket == null) { // Orphaned Approvals, no Futures
207 deleteCW.comment("Approval is Orphaned");
208 Approval.row(deleteCW, appr);
210 // for users and approvers still valid
211 String user = appr.getUser();
213 if(org.isRevoked(noAvg, appr.getApprover())) {
214 deleteCW.comment("Approver ID is revoked");
215 Approval.row(deleteCW, appr);
216 } else if(user!=null && !user.isEmpty() && org.isRevoked(noAvg, user)) {
217 deleteCW.comment("USER ID is revoked");
218 Approval.row(deleteCW, appr);
220 ticket.approvals.add(appr); // add to found Ticket
221 approvers.add(appr.getApprover());
229 /* Run through all Futures, and see if
230 * 1) they have been executed (no longer valid)
231 * 2) The current Approvals indicate they can proceed
233 Map<String,Pending> pendingApprs = new HashMap<>();
234 Map<String,Pending> pendingTemp = new HashMap<>();
238 tt = trans.start("Analyze Good Tickets",Trans.SUB);
240 for(Ticket ticket : goodTickets.values()) {
243 switch(ticket.f.target()) {
245 int state[][] = new int[3][3];
248 for(Approval appr : ticket.approvals) {
249 switch(appr.getType()) {
259 ++state[type][total]; // count per type
260 switch(appr.getStatus()) {
262 ++state[type][pending];
263 approver = appr.getApprover();
264 Pending n = pendingTemp.get(approver);
266 Date lastNotified = ln.lastNotified(approver,"pending",null);
267 pendingTemp.put(approver,new Pending(lastNotified));
273 ++state[type][approved];
276 ++state[type][unknown];
281 // Always must have at least 1 owner
282 if((state[owner][total]>0 && state[owner][approved]>0) &&
283 // If there are no Supervisors, that's ok
284 (state[supervisor][total]==0 ||
285 // But if there is a Supervisor, they must have approved
286 (state[supervisor][approved]>0))) {
287 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
289 urdd.reconstitute(ticket.f.fdd.construct);
290 if(urdd.expires.before(ticket.f.expires())) {
291 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
293 } catch (IOException e) {
294 trans.error().log("Could not reconstitute UserRole");
296 } else { // Load all the Pending.
297 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
298 Pending p = pendingApprs.get(es.getKey());
300 pendingApprs.put(es.getKey(), es.getValue());
302 p.inc(es.getValue());
309 if("user_role".equals(ticket.f.fdd.target)) {
310 String key = ticket.f.fdd.target_key;
312 mur.put(key, ticket);
320 // Good Tickets no longer needed
324 * Decide to Notify about Approvals, based on activity/last Notified
326 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
328 GregorianCalendar gc = new GregorianCalendar();
329 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
330 Date remind = gc.getTime();
332 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
333 Pending p = es.getValue();
335 || p.earliest() == LastNotified.NEVER // yes, equals.
336 || p.earliest().after(remind)) {
337 p.row(needApproveCW,es.getKey());
344 // clear out Approval Intermediates
351 Run through User Roles.
352 Owners are treated specially in next section.
353 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
357 Role.load(trans, session);
360 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
361 Set<String> specialCommented = new HashSet<>();
362 Map<String, Set<UserRole>> owners = new TreeMap<>();
364 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
367 identity = trans.org().getIdentity(noAvg,ur.user());
369 // Candidate for Delete, but not Users if Special
370 String id = ur.user();
371 for(String s : specialDomains) {
373 if(!specialCommented.contains(id)) {
374 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
375 specialCommented.add(id);
380 if(specialNames.contains(id)) {
381 if(!specialCommented.contains(id)) {
382 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
383 specialCommented.add(id);
387 ur.row(deleteCW, UserRole.UR,"Not in Organization");
389 } else if(Role.byName.get(ur.role())==null) {
390 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
393 // Just let expired UserRoles sit until deleted
394 if(futureRange.inRange(ur.expires())&&(!mur.containsKey(ur.user() + '|' + ur.role()))) {
395 // Cannot just delete owners, unless there is at least one left. Process later
396 if ("owner".equals(ur.rname())) {
397 Set<UserRole> urs = owners.get(ur.role());
399 urs = new HashSet<UserRole>();
400 owners.put(ur.role(), urs);
404 Range r = writeAnalysis(noAvg,ur);
406 Approval existing = findApproval(ur);
408 ur.row(needApproveCW,UserRole.APPROVE_UR);
413 } catch (OrganizationException e) {
414 noAvg.error().log(e);
423 Now Process Owners, one owner Role at a time, ensuring one is left,
424 preferably a good one. If so, process the others as normal.
426 Otherwise, write to ExpiredOwners Report
428 tt = trans.start("Analyze Owners Separately",Trans.SUB);
430 if (!owners.values().isEmpty()) {
431 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
432 final CSV ownerCSV = new CSV(env.access(),file);
433 CSV.Writer expOwner = ownerCSV.writer();
434 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
437 for (Set<UserRole> sur : owners.values()) {
439 for (UserRole ur : sur) {
440 if (ur.expires().after(now)) {
445 for (UserRole ur : sur) {
446 if (goodOwners >= minOwners) {
447 Range r = writeAnalysis(noAvg, ur);
449 Approval existing = findApproval(ur);
451 ur.row(needApproveCW,UserRole.APPROVE_UR);
455 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
456 Approval existing = findApproval(ur);
458 ur.row(needApproveCW,UserRole.APPROVE_UR);
478 * Check for Expired Credentials
481 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
482 Cred.load(trans, session);
484 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
486 for (Cred cred : Cred.data.values()) {
487 List<Instance> linst = cred.instances;
489 Instance lastBath = null;
490 for(Instance inst : linst) {
491 // All Creds go through Life Cycle
492 if(deleteDate!=null && inst.expires.before(deleteDate)) {
493 writeAnalysis(noAvg, cred, inst); // will go to Delete
494 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
495 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
496 if(lastBath==null || lastBath.expires.before(inst.expires)) {
502 writeAnalysis(noAvg, cred, lastBath);
514 tt = trans.start("Analyze Expired X509s",Trans.SUB);
516 X509.load(noAvg, session, x509 -> {
518 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
519 writeAnalysis(noAvg, x509, (X509Certificate)cert);
521 } catch (CertificateException | IOException e) {
522 noAvg.error().log(e, "Error Decrypting X509");
528 } catch (FileNotFoundException e) {
533 private Approval findApproval(UserRole ur) {
534 Approval existing = null;
535 List<Approval> apprs = Approval.byUser.get(ur.user());
537 for(Approval appr : apprs) {
538 if(ur.role().equals(appr.getRole()) &&
539 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
547 private Range writeAnalysis(AuthzTrans noAvg, UserRole ur) {
548 Range r = expireRange.getRange("ur", ur.expires());
550 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
551 // Note: lnd is NEVER null
554 i = org.getIdentity(noAvg, ur.user());
555 } catch (OrganizationException e) {
558 if(r.needsContact(lnd,i)) {
559 CSV.Writer cw = writerList.get(r.name());
561 ur.row(cw,UserRole.UR);
568 private void writeAnalysis(AuthzTrans noAvg, Cred cred, Instance inst) {
569 if(cred!=null && inst!=null) {
570 Range r = expireRange.getRange("cred", inst.expires);
572 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
573 // Note: lnd is NEVER null
576 i = org.getIdentity(noAvg, cred.id);
577 } catch (OrganizationException e) {
580 if(r.needsContact(lnd,i)) {
581 CSV.Writer cw = writerList.get(r.name());
590 private void writeAnalysis(AuthzTrans noAvg, X509 x509, X509Certificate x509Cert) throws IOException {
591 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
593 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
594 // Note: lnd is NEVER null
597 i = org.getIdentity(noAvg, x509.id);
598 } catch (OrganizationException e) {
601 if(r.needsContact(lnd,i)) {
602 CSV.Writer cw = writerList.get(r.name());
604 x509.row(cw,x509Cert);
611 protected void _close(AuthzTrans trans) {
613 for(CSV.Writer cw : writerList.values()) {