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;
95 public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
97 trans.info().log("Starting Connection Process");
99 TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
101 TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
103 session = cluster.connect();
108 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
109 Cred.load(trans, session);
113 // Create Intermediate Output
114 writerList = new HashMap<>();
116 expireRange = new ExpireRange(trans.env().access());
117 sdate = Chrono.dateOnlyStamp(now);
118 for( List<Range> lr : expireRange.ranges.values()) {
120 if(writerList.get(r.name())==null) {
121 File file = new File(logDir(),r.name() + sdate +CSV);
122 CSV csv = new CSV(env.access(),file);
123 CSV.Writer cw = csv.writer(false);
124 cw.row(INFO,r.name(),sdate,r.reportingLevel());
125 writerList.put(r.name(),cw);
126 if("Delete".equals(r.name())) {
127 deleteDate = r.getEnd();
130 trans.init().log("Creating File:",file.getAbsolutePath());
135 // Setup New Approvals file
136 futureRange = ExpireRange.newFutureRange();
137 File file = new File(logDir(),NEED_APPROVALS + sdate +CSV);
138 CSV approveCSV = new CSV(env.access(),file);
139 needApproveCW = approveCSV.writer();
140 needApproveCW.row(INFO,NEED_APPROVALS,sdate,1);
141 writerList.put(NEED_APPROVALS,needApproveCW);
143 // Setup Extend Approvals file
144 file = new File(logDir(),EXTEND + sdate +CSV);
145 CSV extendCSV = new CSV(env.access(),file);
146 extendCW = extendCSV.writer();
147 extendCW.row(INFO,EXTEND,sdate,1);
148 writerList.put(EXTEND,extendCW);
150 // Load full data of the following
151 Approval.load(trans, session, Approval.v2_0_17);
152 Role.load(trans, session);
159 protected void run(AuthzTrans trans) {
160 AuthzTrans noAvg = trans.env().newTransNoAvg();
163 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
164 TimeTaken tt = trans.start("Analyze Expired Futures",Trans.SUB);
166 Future.load(noAvg, session, Future.withConstruct, fut -> {
167 List<Approval> appls = Approval.byTicket.get(fut.id());
168 if(!futureRange.inRange(fut.expires())) {
169 deleteCW.comment("Future %s expired", fut.id());
170 Future.row(deleteCW,fut);
172 for(Approval a : appls) {
173 Approval.row(deleteCW, a);
176 } else if(appls==null) { // Orphaned Future (no Approvals)
177 deleteCW.comment("Future is Orphaned");
178 Future.row(deleteCW,fut);
180 goodTickets.put(fut.fdd.id, new Ticket(fut));
187 Set<String> approvers = new TreeSet<>();
188 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
190 for(Approval appr : Approval.list) {
192 UUID ticketID = appr.getTicket();
194 ticket = goodTickets.get(appr.getTicket());
196 if(ticket == null) { // Orphaned Approvals, no Futures
197 deleteCW.comment("Approval is Orphaned");
198 Approval.row(deleteCW, appr);
200 ticket.approvals.add(appr); // add to found Ticket
201 approvers.add(appr.getApprover());
208 /* Run through all Futures, and see if
209 * 1) they have been executed (no longer valid)
210 * 2) The current Approvals indicate they can proceed
212 Map<String,Pending> pendingApprs = new HashMap<>();
213 Map<String,Pending> pendingTemp = new HashMap<>();
215 // Convert Good Tickets to keyed User/Role for UserRole Step
216 Map<String,Ticket> mur = new TreeMap<>();
217 LastNotified ln = new LastNotified(session);
221 tt = trans.start("Analyze Good Tickets",Trans.SUB);
223 for(Ticket ticket : goodTickets.values()) {
226 switch(ticket.f.target()) {
228 int state[][] = new int[3][3];
231 for(Approval appr : ticket.approvals) {
232 switch(appr.getType()) {
242 ++state[type][total]; // count per type
243 switch(appr.getStatus()) {
245 ++state[type][pending];
246 approver = appr.getApprover();
247 Pending n = pendingTemp.get(approver);
249 Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key);
250 pendingTemp.put(approver,new Pending(lastNotified));
256 ++state[type][approved];
259 ++state[type][unknown];
264 // Always must have at least 1 owner
265 if((state[owner][total]>0 && state[owner][approved]>0) &&
266 // If there are no Supervisors, that's ok
267 (state[supervisor][total]==0 ||
268 // But if there is a Supervisor, they must have approved
269 (state[supervisor][approved]>0))) {
270 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
272 urdd.reconstitute(ticket.f.fdd.construct);
273 if(urdd.expires.before(ticket.f.expires())) {
274 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
276 } catch (IOException e) {
277 trans.error().log("Could not reconstitute UserRole");
279 } else { // Load all the Pending.
280 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
281 Pending p = pendingApprs.get(es.getKey());
283 pendingApprs.put(es.getKey(), es.getValue());
285 p.inc(es.getValue());
292 if("user_role".equals(ticket.f.fdd.target)) {
293 String key = ticket.f.fdd.target_key;
295 mur.put(key, ticket);
304 // Good Tickets no longer needed
308 * Decide to Notify about Approvals, based on activity/last Notified
310 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
312 GregorianCalendar gc = new GregorianCalendar();
313 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
314 Date remind = gc.getTime();
316 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
317 Pending p = es.getValue();
319 || p.earliest() == null
320 || p.earliest().after(remind)) {
321 p.row(needApproveCW,es.getKey());
328 // clear out Approval Intermediates
333 Run through User Roles.
334 Owners are treated specially in next section.
335 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
338 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
339 Set<String> specialCommented = new HashSet<>();
340 Map<String, Set<UserRole>> owners = new TreeMap<>();
342 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
345 identity = trans.org().getIdentity(noAvg,ur.user());
347 // Candidate for Delete, but not Users if Special
348 String id = ur.user();
349 for(String s : specialDomains) {
351 if(!specialCommented.contains(id)) {
352 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
353 specialCommented.add(id);
358 if(specialNames.contains(id)) {
359 if(!specialCommented.contains(id)) {
360 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
361 specialCommented.add(id);
365 ur.row(deleteCW, UserRole.UR,"Not in Organization");
367 } else if(Role.byName.get(ur.role())==null) {
368 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
371 // Just let expired UserRoles sit until deleted
372 if(futureRange.inRange(ur.expires())) {
373 if(!mur.containsKey(ur.user() + '|' + ur.role())) {
374 // Cannot just delete owners, unless there is at least one left. Process later
375 if ("owner".equals(ur.rname())) {
376 Set<UserRole> urs = owners.get(ur.role());
378 urs = new HashSet<UserRole>();
379 owners.put(ur.role(), urs);
383 Range r = writeAnalysis(noAvg,ur);
385 Approval existing = findApproval(ur);
387 ur.row(needApproveCW,UserRole.APPROVE_UR);
393 } catch (OrganizationException e) {
394 noAvg.error().log(e);
402 Now Process Owners, one owner Role at a time, ensuring one is left,
403 preferably a good one. If so, process the others as normal.
405 Otherwise, write to ExpiredOwners Report
407 tt = trans.start("Analyze Owners Separately",Trans.SUB);
409 if (!owners.values().isEmpty()) {
410 File file = new File(logDir(), EXPIRED_OWNERS + sdate + CSV);
411 final CSV ownerCSV = new CSV(env.access(),file);
412 CSV.Writer expOwner = ownerCSV.writer();
413 expOwner.row(INFO,EXPIRED_OWNERS,sdate,2);
416 for (Set<UserRole> sur : owners.values()) {
418 for (UserRole ur : sur) {
419 if (ur.expires().after(now)) {
424 for (UserRole ur : sur) {
425 if (goodOwners >= minOwners) {
426 Range r = writeAnalysis(noAvg, ur);
428 Approval existing = findApproval(ur);
430 ur.row(needApproveCW,UserRole.APPROVE_UR);
434 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
435 Approval existing = findApproval(ur);
437 ur.row(needApproveCW,UserRole.APPROVE_UR);
453 * Check for Expired Credentials
457 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
459 for (Cred cred : Cred.data.values()) {
460 List<Instance> linst = cred.instances;
462 Instance lastBath = null;
463 for(Instance inst : linst) {
465 // writeAnalysis(trans, cred, inst);
466 // // Special Behavior: only eval the LAST Instance
468 // All Creds go through Life Cycle
469 if(deleteDate!=null && inst.expires.before(deleteDate)) {
470 writeAnalysis(noAvg, cred, inst); // will go to Delete
471 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
472 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
473 if(lastBath==null || lastBath.expires.before(inst.expires)) {
479 writeAnalysis(noAvg, cred, lastBath);
488 tt = trans.start("Analyze Expired X509s",Trans.SUB);
490 X509.load(noAvg, session, x509 -> {
492 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
493 writeAnalysis(noAvg, x509, (X509Certificate)cert);
495 } catch (CertificateException | IOException e) {
496 noAvg.error().log(e, "Error Decrypting X509");
502 } catch (FileNotFoundException e) {
507 private Approval findApproval(UserRole ur) {
508 Approval existing = null;
509 List<Approval> apprs = Approval.byUser.get(ur.user());
511 for(Approval appr : apprs) {
512 if(ur.role().equals(appr.getRole()) &&
513 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
521 private Range writeAnalysis(AuthzTrans trans, UserRole ur) {
522 Range r = expireRange.getRange("ur", ur.expires());
524 CSV.Writer cw = writerList.get(r.name());
526 ur.row(cw,UserRole.UR);
532 private void writeAnalysis(AuthzTrans trans, Cred cred, Instance inst) {
533 if(cred!=null && inst!=null) {
534 Range r = expireRange.getRange("cred", inst.expires);
536 CSV.Writer cw = writerList.get(r.name());
544 private void writeAnalysis(AuthzTrans trans, X509 x509, X509Certificate x509Cert) throws IOException {
545 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
547 CSV.Writer cw = writerList.get(r.name());
549 x509.row(cw,x509Cert);
555 protected void _close(AuthzTrans trans) {
557 for(CSV.Writer cw : writerList.values()) {