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.UUID;
43 import org.onap.aaf.auth.batch.Batch;
44 import org.onap.aaf.auth.batch.approvalsets.Pending;
45 import org.onap.aaf.auth.batch.approvalsets.Ticket;
46 import org.onap.aaf.auth.batch.helpers.Approval;
47 import org.onap.aaf.auth.batch.helpers.Cred;
48 import org.onap.aaf.auth.batch.helpers.Cred.Instance;
49 import org.onap.aaf.auth.batch.helpers.ExpireRange;
50 import org.onap.aaf.auth.batch.helpers.ExpireRange.Range;
51 import org.onap.aaf.auth.batch.helpers.Future;
52 import org.onap.aaf.auth.batch.helpers.Role;
53 import org.onap.aaf.auth.batch.helpers.UserRole;
54 import org.onap.aaf.auth.batch.helpers.X509;
55 import org.onap.aaf.auth.dao.cass.CredDAO;
56 import org.onap.aaf.auth.dao.cass.UserRoleDAO;
57 import org.onap.aaf.auth.env.AuthzTrans;
58 import org.onap.aaf.auth.org.Organization.Identity;
59 import org.onap.aaf.auth.org.OrganizationException;
60 import org.onap.aaf.cadi.configure.Factory;
61 import org.onap.aaf.cadi.util.CSV;
62 import org.onap.aaf.misc.env.APIException;
63 import org.onap.aaf.misc.env.Env;
64 import org.onap.aaf.misc.env.TimeTaken;
65 import org.onap.aaf.misc.env.Trans;
66 import org.onap.aaf.misc.env.util.Chrono;
69 public class Analyze extends Batch {
70 private static final int unknown=0;
71 private static final int owner=1;
72 private static final int supervisor=2;
73 private static final int total=0;
74 private static final int pending=1;
75 private static final int approved=2;
78 private static final String APPROVALS = "Approvals";
79 private static final String EXTEND = "Extend";
80 private static final String EXPIRED_OWNERS = "ExpiredOwners";
81 private static final String CSV = ".csv";
82 private static final String INFO = "info";
83 private int minOwners;
84 private Map<String, CSV.Writer> writerList;
85 private ExpireRange expireRange;
86 private Date deleteDate;
87 private CSV.Writer deleteCW;
88 private CSV.Writer approveCW;
89 private CSV.Writer extendCW;
91 public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
93 trans.info().log("Starting Connection Process");
95 TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
97 TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
99 session = cluster.connect();
104 // Load Cred. We don't follow Visitor, because we have to gather up everything into Identity Anyway
105 Cred.load(trans, session);
109 // Create Intermediate Output
110 writerList = new HashMap<>();
112 expireRange = new ExpireRange(trans.env().access());
113 String sdate = Chrono.dateOnlyStamp(expireRange.now);
114 for( List<Range> lr : expireRange.ranges.values()) {
116 if(writerList.get(r.name())==null) {
117 File file = new File(logDir(),r.name() + sdate +CSV);
118 CSV csv = new CSV(env.access(),file);
119 CSV.Writer cw = csv.writer(false);
120 cw.row(INFO,r.name(),Chrono.dateOnlyStamp(expireRange.now),r.reportingLevel());
121 writerList.put(r.name(),cw);
122 if("Delete".equals(r.name())) {
123 deleteDate = r.getEnd();
126 trans.init().log("Creating File:",file.getAbsolutePath());
131 // Setup New Approvals file
132 File file = new File(logDir(),APPROVALS + sdate +CSV);
133 CSV approveCSV = new CSV(env.access(),file);
134 approveCW = approveCSV.writer();
135 approveCW.row(INFO,APPROVALS,Chrono.dateOnlyStamp(expireRange.now),1);
136 writerList.put(APPROVALS,approveCW);
138 // Setup Extend Approvals file
139 file = new File(logDir(),EXTEND + sdate +CSV);
140 CSV extendCSV = new CSV(env.access(),file);
141 extendCW = extendCSV.writer();
142 extendCW.row(INFO,EXTEND,Chrono.dateOnlyStamp(expireRange.now),1);
143 writerList.put(EXTEND,extendCW);
145 // Load full data of the following
146 Approval.load(trans, session, Approval.v2_0_17);
147 Role.load(trans, session);
154 protected void run(AuthzTrans trans) {
155 AuthzTrans noAvg = trans.env().newTransNoAvg();
158 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
159 TimeTaken tt = trans.start("Analyze Expired Futures",Trans.SUB);
161 Future.load(noAvg, session, Future.withConstruct, fut -> {
162 List<Approval> appls = Approval.byTicket.get(fut.id());
163 if(fut.expires().before(expireRange.now)) {
164 deleteCW.comment("Future %s expired", fut.id());
165 Future.row(deleteCW,fut);
167 for(Approval a : appls) {
168 Approval.row(deleteCW, a);
171 } else if(appls==null) { // Orphaned Future (no Approvals)
172 deleteCW.comment("Future is Orphaned");
173 Future.row(deleteCW,fut);
175 goodTickets.put(fut.fdd.id, new Ticket(fut));
182 tt = trans.start("Connect Approvals with Futures",Trans.SUB);
184 for(Approval appr : Approval.list) {
186 UUID ticketID = appr.getTicket();
188 ticket = goodTickets.get(appr.getTicket());
190 if(ticket == null) { // Orphaned Approvals, no Futures
191 deleteCW.comment("Approval is Orphaned");
192 Approval.row(deleteCW, appr);
194 ticket.approvals.add(appr); // add to found Ticket
201 /* Run through all Futures, and see if
202 * 1) they have been executed (no longer valid)
203 * 2) The current Approvals indicate they can proceed
205 Map<String,Pending> pendingApprs = new HashMap<>();
206 Map<String,Pending> pendingTemp = new HashMap<>();
208 tt = trans.start("Analyze Good Tickets",Trans.SUB);
210 for(Ticket ticket : goodTickets.values()) {
212 switch(ticket.f.target()) {
214 int state[][] = new int[3][3];
217 for(Approval appr : ticket.approvals) {
218 switch(appr.getType()) {
228 ++state[type][total]; // count per type
229 switch(appr.getStatus()) {
231 ++state[type][pending];
232 Pending n = pendingTemp.get(appr.getApprover());
234 pendingTemp.put(appr.getApprover(),new Pending());
240 ++state[type][approved];
243 ++state[type][unknown];
248 // Always must have at least 1 owner
249 if((state[owner][total]>0 && state[owner][approved]>0) &&
250 // If there are no Supervisors, that's ok
251 (state[supervisor][total]==0 ||
252 // But if there is a Supervisor, they must have approved
253 (state[supervisor][approved]>0))) {
254 UserRoleDAO.Data urdd = new UserRoleDAO.Data();
256 urdd.reconstitute(ticket.f.fdd.construct);
257 if(urdd.expires.before(ticket.f.expires())) {
258 extendCW.row("extend_ur",urdd.user,urdd.role,ticket.f.expires());
260 } catch (IOException e) {
261 trans.error().log("Could not reconstitute UserRole");
263 } else { // Load all the Pending.
264 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
265 Pending p = pendingApprs.get(es.getKey());
267 pendingApprs.put(es.getKey(), es.getValue());
269 p.inc(es.getValue());
281 * Decide to Notify about Approvals, based on activity/last Notified
283 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
285 GregorianCalendar gc = new GregorianCalendar();
286 gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
287 Date remind = gc.getTime();
289 for(Entry<String, Pending> es : pendingApprs.entrySet()) {
290 Pending p = es.getValue();
291 if(p.newApprovals() || p.earliest() == null || p.earliest().after(remind)) {
292 p.row(approveCW,es.getKey());
299 // clear out Approval Intermediates
305 Run through User Roles.
306 Owners are treated specially in next section.
307 Regular roles are checked against Date Ranges. If match Date Range, write out to appropriate file.
310 tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
311 Set<String> specialCommented = new HashSet<>();
312 Map<String, Set<UserRole>> owners = new TreeMap<String, Set<UserRole>>();
314 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
317 identity = trans.org().getIdentity(noAvg,ur.user());
319 // Candidate for Delete, but not Users if Special
320 String id = ur.user();
321 for(String s : specialDomains) {
323 if(!specialCommented.contains(id)) {
324 deleteCW.comment("ID %s is part of special Domain %s (UR Org Check)", id,s);
325 specialCommented.add(id);
330 if(specialNames.contains(id)) {
331 if(!specialCommented.contains(id)) {
332 deleteCW.comment("ID %s is a special ID (UR Org Check)", id);
333 specialCommented.add(id);
337 ur.row(deleteCW, UserRole.UR,"Not in Organization");
339 } else if(Role.byName.get(ur.role())==null) {
340 ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
343 // Cannot just delete owners, unless there is at least one left. Process later
344 if ("owner".equals(ur.rname())) {
345 Set<UserRole> urs = owners.get(ur.role());
347 urs = new HashSet<UserRole>();
348 owners.put(ur.role(), urs);
352 Range r = writeAnalysis(noAvg,ur);
354 Approval existing = findApproval(ur);
356 ur.row(approveCW,UserRole.APPROVE_UR);
360 } catch (OrganizationException e) {
361 noAvg.error().log(e);
369 Now Process Owners, one owner Role at a time, ensuring one is left,
370 preferably a good one. If so, process the others as normal.
372 Otherwise, write to ExpiredOwners Report
374 tt = trans.start("Analyze Owners Separately",Trans.SUB);
376 if (!owners.values().isEmpty()) {
377 File file = new File(logDir(), EXPIRED_OWNERS + Chrono.dateOnlyStamp(expireRange.now) + CSV);
378 final CSV ownerCSV = new CSV(env.access(),file);
379 CSV.Writer expOwner = ownerCSV.writer();
380 expOwner.row(INFO,EXPIRED_OWNERS,Chrono.dateOnlyStamp(expireRange.now),2);
383 for (Set<UserRole> sur : owners.values()) {
385 for (UserRole ur : sur) {
386 if (ur.expires().after(expireRange.now)) {
391 for (UserRole ur : sur) {
392 if (goodOwners >= minOwners) {
393 Range r = writeAnalysis(noAvg, ur);
395 Approval existing = findApproval(ur);
397 ur.row(approveCW,UserRole.APPROVE_UR);
401 expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
402 Approval existing = findApproval(ur);
404 ur.row(approveCW,UserRole.APPROVE_UR);
420 * Check for Expired Credentials
424 tt = trans.start("Analyze Expired Credentials",Trans.SUB);
426 for (Cred cred : Cred.data.values()) {
427 List<Instance> linst = cred.instances;
429 Instance lastBath = null;
430 for(Instance inst : linst) {
432 // writeAnalysis(trans, cred, inst);
433 // // Special Behavior: only eval the LAST Instance
435 // All Creds go through Life Cycle
436 if(deleteDate!=null && inst.expires.before(deleteDate)) {
437 writeAnalysis(noAvg, cred, inst); // will go to Delete
438 // Basic Auth has Pre-EOL notifications IF there is no Newer Credential
439 } else if (inst.type == CredDAO.BASIC_AUTH || inst.type == CredDAO.BASIC_AUTH_SHA256) {
440 if(lastBath==null || lastBath.expires.before(inst.expires)) {
446 writeAnalysis(noAvg, cred, lastBath);
455 tt = trans.start("Analyze Expired X509s",Trans.SUB);
457 X509.load(noAvg, session, x509 -> {
459 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
460 writeAnalysis(noAvg, x509, (X509Certificate)cert);
462 } catch (CertificateException | IOException e) {
463 noAvg.error().log(e, "Error Decrypting X509");
470 } catch (FileNotFoundException e) {
475 private Approval findApproval(UserRole ur) {
476 Approval existing = null;
477 List<Approval> apprs = Approval.byUser.get(ur.user());
479 for(Approval appr : apprs) {
480 if(ur.role().equals(appr.getRole()) &&
481 appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
489 private Range writeAnalysis(AuthzTrans trans, UserRole ur) {
490 Range r = expireRange.getRange("ur", ur.expires());
492 CSV.Writer cw = writerList.get(r.name());
494 ur.row(cw,UserRole.UR);
500 private void writeAnalysis(AuthzTrans trans, Cred cred, Instance inst) {
501 if(cred!=null && inst!=null) {
502 Range r = expireRange.getRange("cred", inst.expires);
504 CSV.Writer cw = writerList.get(r.name());
512 private void writeAnalysis(AuthzTrans trans, X509 x509, X509Certificate x509Cert) throws IOException {
513 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
515 CSV.Writer cw = writerList.get(r.name());
517 x509.row(cw,x509Cert);
523 protected void _close(AuthzTrans trans) {
525 for(CSV.Writer cw : writerList.values()) {