d0b30c7c5b738b2765b177107ad6382865036e05
[aaf/authz.git] / auth / auth-batch / src / main / java / org / onap / aaf / auth / batch / reports / Analyze.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  *
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
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
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====================================================
21  *
22  */
23
24 package org.onap.aaf.auth.batch.reports;
25
26 import java.io.File;
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;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Set;
40 import java.util.TreeMap;
41 import java.util.TreeSet;
42 import java.util.UUID;
43
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;
69
70
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;
78     
79     
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         
95         public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
96         super(trans.env());
97         trans.info().log("Starting Connection Process");
98         
99         TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
100         try {
101             TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
102             try {
103                 session = cluster.connect();
104             } finally {
105                 tt.done();
106             }
107             
108             // Load Cred.  We don't follow Visitor, because we have to gather up everything into Identity Anyway
109             Cred.load(trans, session);
110
111             minOwners=1;
112
113             // Create Intermediate Output 
114             writerList = new HashMap<>();
115             
116             expireRange = new ExpireRange(trans.env().access());
117             sdate = Chrono.dateOnlyStamp(now);
118             for( List<Range> lr : expireRange.ranges.values()) {
119                 for(Range r : lr ) {
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();
128                                 deleteCW = cw;
129                         }
130                         trans.init().log("Creating File:",file.getAbsolutePath());
131                         }
132                 }
133             }
134             
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);
142             
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);
149             
150             // Load full data of the following
151             Approval.load(trans, session, Approval.v2_0_17);
152             Role.load(trans, session);
153         } finally {
154             tt0.done();
155         }
156     }
157
158     @Override
159     protected void run(AuthzTrans trans) {
160         AuthzTrans noAvg = trans.env().newTransNoAvg();
161         
162                 ////////////////////
163                 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
164         TimeTaken tt = trans.start("Analyze Expired Futures",Trans.SUB);
165         try {
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);
171                                         if(appls!=null) {
172                                                 for(Approval a : appls) {
173                                                         Approval.row(deleteCW, a);
174                                                 }
175                                         }
176                                 } else if(appls==null) { // Orphaned Future (no Approvals)
177                                         deleteCW.comment("Future is Orphaned");
178                                         Future.row(deleteCW,fut);
179                                 } else  {
180                                         goodTickets.put(fut.fdd.id, new Ticket(fut));
181                                 }
182                         });
183         } finally {
184                 tt.done();
185         }
186                 
187         Set<String> approvers = new TreeSet<>();
188         tt = trans.start("Connect Approvals with Futures",Trans.SUB);
189         try {
190                         for(Approval appr : Approval.list) {
191                                 Ticket ticket=null;
192                                 UUID ticketID = appr.getTicket();
193                                 if(ticketID!=null) {
194                                         ticket = goodTickets.get(appr.getTicket());
195                                 }
196                                 if(ticket == null) { // Orphaned Approvals, no Futures
197                                         deleteCW.comment("Approval is Orphaned");
198                                         Approval.row(deleteCW, appr);
199                                 } else {
200                                         ticket.approvals.add(appr); // add to found Ticket
201                                         approvers.add(appr.getApprover());
202                                 }
203                         }
204         } finally {
205                 tt.done();
206         }
207
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 
211                  */
212                 Map<String,Pending> pendingApprs = new HashMap<>();
213                 Map<String,Pending> pendingTemp = new HashMap<>();
214
215                 // Convert Good Tickets to keyed User/Role for UserRole Step
216                 Map<String,Ticket> mur = new TreeMap<>();
217                 LastNotified ln = new LastNotified(session);
218                 ln.add(approvers);
219                 String approver;
220                 
221                 tt = trans.start("Analyze Good Tickets",Trans.SUB);
222                 try {
223                         for(Ticket ticket : goodTickets.values()) {
224                                 try {
225                                         pendingTemp.clear();
226                                         switch(ticket.f.target()) {
227                                                 case "user_role":
228                                                         int state[][] = new int[3][3];
229                                                         int type;
230                                                                         
231                                                         for(Approval appr : ticket.approvals) {
232                                                                 switch(appr.getType()) {
233                                                                         case "owner":
234                                                                                 type=owner;
235                                                                                 break;
236                                                                         case "supervisor":
237                                                                                 type=supervisor;
238                                                                                 break;
239                                                                         default:
240                                                                                 type=0;
241                                                                 }
242                                                                 ++state[type][total]; // count per type
243                                                                 switch(appr.getStatus()) {
244                                                                         case "pending":
245                                                                                 ++state[type][pending];
246                                                                                 approver = appr.getApprover();
247                                                                                 Pending n = pendingTemp.get(approver);
248                                                                                 if(n==null) {
249                                                                                         Date lastNotified = ln.lastNotified(approver,"ur",ticket.f.fdd.target_key);
250                                                                                         pendingTemp.put(approver,new Pending(lastNotified));
251                                                                                 } else {
252                                                                                         n.inc();
253                                                                                 }
254                                                                                 break;
255                                                                         case "approved":
256                                                                                 ++state[type][approved];
257                                                                                 break;
258                                                                         default:
259                                                                                 ++state[type][unknown];
260                                                                 }
261                                                         }
262                                                         
263                                                         // To Approve:
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();
271                                                                         try {
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());
275                                                                                 }
276                                                                         } catch (IOException e) {
277                                                                                 trans.error().log("Could not reconstitute UserRole");
278                                                                         }
279                                                         } else { // Load all the Pending.
280                                                                 for(Entry<String, Pending> es : pendingTemp.entrySet()) {
281                                                                         Pending p = pendingApprs.get(es.getKey());
282                                                                         if(p==null) {
283                                                                                 pendingApprs.put(es.getKey(), es.getValue());
284                                                                         } else {
285                                                                                 p.inc(es.getValue());
286                                                                         }
287                                                                 }
288                                                         }
289                                                         break;
290                                         }
291                                 } finally {
292                                         if("user_role".equals(ticket.f.fdd.target)) {
293                                                 String key = ticket.f.fdd.target_key; 
294                                                 if(key!=null) {
295                                                         mur.put(key, ticket);
296                                                 }
297                                         }
298                                 }
299                         }
300                 } finally {
301                         tt.done();
302                 }
303
304                 // Good Tickets no longer needed
305                 goodTickets.clear();
306
307                 /**
308                  * Decide to Notify about Approvals, based on activity/last Notified
309                  */
310                 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
311                 try {
312                         GregorianCalendar gc = new GregorianCalendar();
313                         gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
314                         Date remind = gc.getTime();
315                         
316                         for(Entry<String, Pending> es : pendingApprs.entrySet()) {
317                                 Pending p = es.getValue();
318                                 if(p.newApprovals() 
319                                                 || p.earliest() == null 
320                                                 || p.earliest().after(remind)) {
321                                         p.row(needApproveCW,es.getKey());
322                                 }
323                         }
324                 } finally {
325                         tt.done();
326                 }
327                 
328                 // clear out Approval Intermediates
329                 pendingTemp = null;
330                 pendingApprs = null;
331                 
332                 /**
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.
336                 */              
337                 try {
338                         tt = trans.start("Analyze UserRoles, storing Owners",Trans.SUB);
339                         Set<String> specialCommented = new HashSet<>();
340                         Map<String, Set<UserRole>> owners = new TreeMap<>();
341                         try {
342                                 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
343                                         Identity identity;
344                                         try {
345                                                 identity = trans.org().getIdentity(noAvg,ur.user());
346                                                 if(identity==null) {
347                                                         // Candidate for Delete, but not Users if Special
348                                                         String id = ur.user();
349                                                         for(String s : specialDomains) {
350                                                                 if(id.endsWith(s)) {
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);
354                                                                         }
355                                                                         return;
356                                                                 }
357                                                         }
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);
362                                                                 }
363                                                                 return;
364                                                         }
365                                                         ur.row(deleteCW, UserRole.UR,"Not in Organization");
366                                                         return;
367                                                 } else if(Role.byName.get(ur.role())==null) {
368                                                         ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
369                                                         return;
370                                                 }
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());
377                                                                         if (urs == null) {
378                                                                                 urs = new HashSet<UserRole>();
379                                                                                 owners.put(ur.role(), urs);
380                                                                         }
381                                                                         urs.add(ur);
382                                                                 } else {
383                                                                         Range r = writeAnalysis(noAvg,ur);
384                                                                         if(r!=null) {
385                                                                                 Approval existing = findApproval(ur);
386                                                                                 if(existing==null) {
387                                                                                         ur.row(needApproveCW,UserRole.APPROVE_UR);
388                                                                                 }
389                                                                         }
390                                                                 }
391                                                         }
392                                                 }
393                                         } catch (OrganizationException e) {
394                                                 noAvg.error().log(e);
395                                         }
396                                 });
397                         } finally {
398                                 tt.done();
399                         }
400                 
401                         /**
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. 
404                           
405                           Otherwise, write to ExpiredOwners Report
406                         */
407                         tt = trans.start("Analyze Owners Separately",Trans.SUB);
408                         try {
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);
414
415                                         try {
416                                                 for (Set<UserRole> sur : owners.values()) {
417                                                         int goodOwners = 0;
418                                                         for (UserRole ur : sur) {
419                                                                 if (ur.expires().after(now)) {
420                                                                         ++goodOwners;
421                                                                 }
422                                                         }
423         
424                                                         for (UserRole ur : sur) {
425                                                                 if (goodOwners >= minOwners) {
426                                                                         Range r = writeAnalysis(noAvg, ur);
427                                                                         if(r!=null) {
428                                                                                 Approval existing = findApproval(ur);
429                                                                                 if(existing==null) {
430                                                                                         ur.row(needApproveCW,UserRole.APPROVE_UR);
431                                                                                 }
432                                                                         }
433                                                                 } else {
434                                                                         expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
435                                                                         Approval existing = findApproval(ur);
436                                                                         if(existing==null) {
437                                                                                 ur.row(needApproveCW,UserRole.APPROVE_UR);
438                                                                         }
439                                                                 }
440                                                         }
441                                                 }
442                                         } finally {
443                                                 if(expOwner!=null) {
444                                                         expOwner.close();
445                                                 }
446                                         }
447                                 }
448                         } finally {
449                                 tt.done();
450                         }
451                         
452                         /**
453                          * Check for Expired Credentials
454                          * 
455                          * 
456                          */
457                         tt = trans.start("Analyze Expired Credentials",Trans.SUB);
458                         try {
459                                 for (Cred cred : Cred.data.values()) {
460                                 List<Instance> linst = cred.instances;
461                                 if(linst!=null) {
462                                         Instance lastBath = null;
463                                         for(Instance inst : linst) {
464         //                                      if(inst.attn>0) {
465         //                                              writeAnalysis(trans, cred, inst);
466         //                                              // Special Behavior: only eval the LAST Instance
467         //                                      } else 
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)) {
474                                                                 lastBath = inst;
475                                                         }
476                                                 }
477                                         }
478                                         if(lastBath!=null) {
479                                                 writeAnalysis(noAvg, cred, lastBath);
480                                         }
481                                 }
482                                 }
483                         } finally {
484                                 tt.done();
485                         }
486
487                         ////////////////////
488                         tt = trans.start("Analyze Expired X509s",Trans.SUB);
489                         try {
490                                 X509.load(noAvg, session, x509 -> {
491                                         try {
492                                                 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
493                                                         writeAnalysis(noAvg, x509, (X509Certificate)cert);
494                                                 }
495                                         } catch (CertificateException | IOException e) {
496                                                 noAvg.error().log(e, "Error Decrypting X509");
497                                         }
498                                 });
499                         } finally {
500                                 tt.done();
501                         }
502                 } catch (FileNotFoundException e) {
503                         noAvg.info().log(e);
504                 }
505         }
506  
507         private Approval findApproval(UserRole ur) {
508                 Approval existing = null;
509                 List<Approval> apprs = Approval.byUser.get(ur.user());
510                 if(apprs!=null) {
511                         for(Approval appr : apprs) {
512                                 if(ur.role().equals(appr.getRole()) &&
513                                         appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
514                                                 existing = appr; 
515                                 }
516                         }
517                 }
518                 return existing;
519         }
520
521         private Range writeAnalysis(AuthzTrans trans, UserRole ur) {
522                 Range r = expireRange.getRange("ur", ur.expires());
523                 if(r!=null) {
524                         CSV.Writer cw = writerList.get(r.name());
525                         if(cw!=null) {
526                                 ur.row(cw,UserRole.UR);
527                         }
528                 }
529                 return r;
530         }
531     
532     private void writeAnalysis(AuthzTrans trans, Cred cred, Instance inst) {
533         if(cred!=null && inst!=null) {
534                         Range r = expireRange.getRange("cred", inst.expires);
535                         if(r!=null) {
536                                 CSV.Writer cw = writerList.get(r.name());
537                                 if(cw!=null) {
538                                         cred.row(cw,inst);
539                                 }
540                         }
541         }
542         }
543
544     private void writeAnalysis(AuthzTrans trans, X509 x509, X509Certificate x509Cert) throws IOException {
545                 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
546                 if(r!=null) {
547                         CSV.Writer cw = writerList.get(r.name());
548                         if(cw!=null) {
549                                 x509.row(cw,x509Cert);
550                         }
551                 }
552         }
553     
554     @Override
555     protected void _close(AuthzTrans trans) {
556         session.close();
557         for(CSV.Writer cw : writerList.values()) {
558                 cw.close();
559         }
560     }
561
562 }