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 static final String NOT_COMPLIANT = "NotCompliant";
86 private int minOwners;
87 private Map<String, CSV.Writer> writerList;
88 private ExpireRange expireRange;
89 private Date deleteDate;
90 private CSV.Writer deleteCW;
91 private CSV.Writer needApproveCW;
92 private CSV.Writer extendCW;
93 private CSV.Writer notCompliantCW;
94 private Range futureRange;
95 private final String sdate;
96 private LastNotified ln;
98 public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
100 trans.info().log("Starting Connection Process");
102 TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
104 TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
106 session = cluster.connect();
114 // Create Intermediate Output
115 writerList = new HashMap<>();
117 expireRange = new ExpireRange(trans.env().access());
118 sdate = Chrono.dateOnlyStamp(now);
119 for( List<Range> lr : expireRange.ranges.values()) {
121 if(writerList.get(r.name())==null) {
122 File file = new File(logDir(),r.name() + sdate +CSV);
123 CSV csv = new CSV(env.access(),file);
124 CSV.Writer cw = csv.writer(false);
125 cw.row(INFO,r.name(),sdate,r.reportingLevel());
126 writerList.put(r.name(),cw);
127 if("Delete".equals(r.name())) {
128 deleteDate = r.getEnd();
131 trans.init().log("Creating File:",file.getAbsolutePath());
136 // Setup New Approvals file
137 futureRange = expireRange.newFutureRange();
138 File file = new File(logDir(),NEED_APPROVALS + sdate +CSV);
139 CSV approveCSV = new CSV(env.access(),file);
140 needApproveCW = approveCSV.writer();
141 needApproveCW.row(INFO,NEED_APPROVALS,sdate,1);
142 writerList.put(NEED_APPROVALS,needApproveCW);
144 // Setup Extend Approvals file
145 file = new File(logDir(),EXTEND + sdate +CSV);
146 CSV extendCSV = new CSV(env.access(),file);
147 extendCW = extendCSV.writer();
148 extendCW.row(INFO,EXTEND,sdate,1);
149 writerList.put(EXTEND,extendCW);
151 // Setup NotCompliant Writer for Apps
152 file = new File(logDir(),NOT_COMPLIANT + sdate + CSV);
153 CSV ncCSV = new CSV(env.access(),file);
154 notCompliantCW = ncCSV.writer();
155 writerList.put(NOT_COMPLIANT, notCompliantCW);
157 // Load full data of the following
158 ln = new LastNotified(session);
166 protected void run(AuthzTrans trans) {
168 AuthzTrans noAvg = trans.env().newTransNoAvg();
171 // Load all Notifieds, and either add to local Data, or mark for Deletion.
172 ln.loadAll(noAvg,expireRange.approveDelete,deleteCW);
174 // Hold Good Tickets to keyed User/Role for UserRole Step
175 Map<String,Ticket> mur = new TreeMap<>();
178 Approval.load(trans, session, Approval.v2_0_17);
181 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
182 tt = trans.start("Analyze Expired Futures",Trans.SUB);
184 Future.load(noAvg, session, Future.withConstruct, fut -> {
185 List<Approval> appls = Approval.byTicket.get(fut.id());
186 if(!futureRange.inRange(fut.expires())) {
187 deleteCW.comment("Future %s expired", fut.id());
188 Future.row(deleteCW,fut);
190 for(Approval a : appls) {
191 Approval.row(deleteCW, a);
194 } else if(appls==null) { // Orphaned Future (no Approvals)
195 deleteCW.comment("Future is Orphaned");
196 Future.row(deleteCW,fut);
198 goodTickets.put(fut.fdd.id, new Ticket(fut));
205 Set<String> approvers = new TreeSet<>();
206 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
208 for(Approval appr : Approval.list) {
210 UUID ticketID = appr.getTicket();
212 ticket = goodTickets.get(appr.getTicket());
214 if(ticket == null) { // Orphaned Approvals, no Futures
215 deleteCW.comment("Approval is Orphaned");
216 Approval.row(deleteCW, appr);
218 // for users and approvers still valid
219 String user = appr.getUser();
221 if(org.isRevoked(noAvg, appr.getApprover())) {
222 deleteCW.comment("Approver ID is revoked");
223 Approval.row(deleteCW, appr);
224 } else if(user!=null && !user.isEmpty() && org.isRevoked(noAvg, user)) {
225 deleteCW.comment("USER ID is revoked");
226 Approval.row(deleteCW, appr);
228 ticket.approvals.add(appr); // add to found Ticket
229 approvers.add(appr.getApprover());
237 /* Run through all Futures, and see if
238 * 1) they have been executed (no longer valid)
239 * 2) The current Approvals indicate they can proceed
241 Map<String,Pending> pendingApprs = new HashMap<>();
242 Map<String,Pending> pendingTemp = new HashMap<>();
246 tt = trans.start("Analyze Good Tickets",Trans.SUB);
248 for(Ticket ticket : goodTickets.values()) {
251 switch(ticket.f.target()) {
253 int state[][] = new int[3][3];
256 for(Approval appr : ticket.approvals) {
257 switch(appr.getType()) {
267 ++state[type][total]; // count per type
268 switch(appr.getStatus()) {
270 ++state[type][pending];
271 approver = appr.getApprover();
272 Pending n = pendingTemp.get(approver);
274 Date lastNotified = ln.lastNotified(approver,"pending",null);
275 pendingTemp.put(approver,new Pending(lastNotified));
281 ++state[type][approved];
284 ++state[type][unknown];
289 // Always must have at least 1 owner
290 if((state[owner][total]>0 && state[owner][approved]>0) &&
291 // If there are no Supervisors, that's ok
292 (state[supervisor][total]==0 ||
293 // But if there is a Supervisor, they must have approved
294 (state[supervisor][approved]>0))) {
295 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
297 urdd.reconstitute(ticket.f.fdd.construct);
298 if(urdd.expires.before(ticket.f.expires())) {
299 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
301 } catch (IOException e) {
302 trans.error().log("Could not reconstitute UserRole");
304 } else { // Load all the Pending.
305 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
306 Pending p = pendingApprs.get(es.getKey());
308 pendingApprs.put(es.getKey(), es.getValue());
310 p.inc(es.getValue());
317 if("user_role".equals(ticket.f.fdd.target)) {
318 String key = ticket.f.fdd.target_key;
320 mur.put(key, ticket);
328 // Good Tickets no longer needed
332 * Decide to Notify about Approvals, based on activity/last Notified
334 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
336 GregorianCalendar gc = new GregorianCalendar();
337 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
338 Date remind = gc.getTime();
340 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
341 Pending p = es.getValue();
343 || p.earliest() == LastNotified.NEVER // yes, equals.
344 || p.earliest().after(remind)) {
345 p.row(needApproveCW,es.getKey());
352 // clear out Approval Intermediates
359 Run through User Roles.
360 Owners are treated specially in next section.
361 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
365 Role.load(trans, session);
368 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
369 Set<String> specialCommented = new HashSet<>();
370 Map<String, Set<UserRole>> owners = new TreeMap<>();
372 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
375 identity = trans.org().getIdentity(noAvg,ur.user());
377 // Candidate for Delete, but not Users if Special
378 String id = ur.user();
379 for(String s : specialDomains) {
381 if(!specialCommented.contains(id)) {
382 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
383 specialCommented.add(id);
388 if(specialNames.contains(id)) {
389 if(!specialCommented.contains(id)) {
390 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
391 specialCommented.add(id);
395 ur.row(deleteCW, UserRole.UR,"Not in Organization");
397 } else if(Role.byName.get(ur.role())==null) {
398 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
401 // Just let expired UserRoles sit until deleted
402 if(futureRange.inRange(ur.expires())&&(!mur.containsKey(ur.user() + '|' + ur.role()))) {
403 // Cannot just delete owners, unless there is at least one left. Process later
404 if ("owner".equals(ur.rname())) {
405 Set<UserRole> urs = owners.get(ur.role());
407 urs = new HashSet<UserRole>();
408 owners.put(ur.role(), urs);
412 Range r = writeAnalysis(noAvg,ur);
414 Approval existing = findApproval(ur);
416 ur.row(needApproveCW,UserRole.APPROVE_UR);
421 } catch (OrganizationException e) {
422 noAvg.error().log(e);
431 Now Process Owners, one owner Role at a time, ensuring one is left,
432 preferably a good one. If so, process the others as normal.
434 Otherwise, write to ExpiredOwners Report
436 tt = trans.start("Analyze Owners Separately",Trans.SUB);
438 if (!owners.values().isEmpty()) {
439 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
440 final CSV ownerCSV = new CSV(env.access(),file);
441 CSV.Writer expOwner = ownerCSV.writer();
442 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
445 for (Set<UserRole> sur : owners.values()) {
447 for (UserRole ur : sur) {
448 if (ur.expires().after(now)) {
453 for (UserRole ur : sur) {
454 if (goodOwners >= minOwners) {
455 Range r = writeAnalysis(noAvg, ur);
457 Approval existing = findApproval(ur);
459 ur.row(needApproveCW,UserRole.APPROVE_UR);
463 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
464 Approval existing = findApproval(ur);
466 ur.row(needApproveCW,UserRole.APPROVE_UR);
486 * Check for Expired Credentials
489 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
490 Cred.load(trans, session);
492 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
494 for (Cred cred : Cred.data.values()) {
495 List<Instance> linst = cred.instances;
497 Instance lastBath = null;
498 for(Instance inst : linst) {
499 // All Creds go through Life Cycle
500 if(deleteDate!=null && inst.expires.before(deleteDate)) {
501 writeAnalysis(noAvg, cred, inst); // will go to Delete
502 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
503 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
504 if(lastBath==null || lastBath.expires.before(inst.expires)) {
510 writeAnalysis(noAvg, cred, lastBath);
522 tt = trans.start("Analyze Expired X509s",Trans.SUB);
524 X509.load(noAvg, session, x509 -> {
526 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
527 writeAnalysis(noAvg, x509, (X509Certificate)cert);
529 } catch (CertificateException | IOException e) {
530 noAvg.error().log(e, "Error Decrypting X509");
536 } catch (FileNotFoundException e) {
541 private Approval findApproval(UserRole ur) {
542 Approval existing = null;
543 List<Approval> apprs = Approval.byUser.get(ur.user());
545 for(Approval appr : apprs) {
546 if(ur.role().equals(appr.getRole()) &&
547 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
555 private Range writeAnalysis(AuthzTrans noAvg, UserRole ur) {
556 Range r = expireRange.getRange("ur", ur.expires());
558 Date lnd = ln.lastNotified(LastNotified.newKey(ur));
559 // Note: lnd is NEVER null
562 i = org.getIdentity(noAvg, ur.user());
563 } catch (OrganizationException e) {
566 if(r.needsContact(lnd,i)) {
567 CSV.Writer cw = writerList.get(r.name());
569 ur.row(cw,UserRole.UR);
576 private void writeAnalysis(AuthzTrans noAvg, Cred cred, Instance inst) {
577 if(cred!=null && inst!=null) {
578 Range r = expireRange.getRange("cred", inst.expires);
580 Date lnd = ln.lastNotified(LastNotified.newKey(cred,inst));
581 // Note: lnd is NEVER null
584 i = org.getIdentity(noAvg, cred.id);
585 } catch (OrganizationException e) {
588 if(r.needsContact(lnd,i)) {
589 CSV.Writer cw = writerList.get(r.name());
598 private void writeAnalysis(AuthzTrans noAvg, X509 x509, X509Certificate x509Cert) throws IOException {
599 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
601 Date lnd = ln.lastNotified(LastNotified.newKey(x509,x509Cert));
602 // Note: lnd is NEVER null
605 i = org.getIdentity(noAvg, x509.id);
606 } catch (OrganizationException e) {
609 if(r.needsContact(lnd,i)) {
610 CSV.Writer cw = writerList.get(r.name());
612 x509.row(cw,x509Cert);
619 protected void _close(AuthzTrans trans) {
621 for(CSV.Writer cw : writerList.values()) {