Batch work and client
[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.UUID;
42
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;
67
68
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;
76     
77     
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;
90         
91         public Analyze(AuthzTrans trans) throws APIException, IOException, OrganizationException {
92         super(trans.env());
93         trans.info().log("Starting Connection Process");
94         
95         TimeTaken tt0 = trans.start("Cassandra Initialization", Env.SUB);
96         try {
97             TimeTaken tt = trans.start("Connect to Cluster", Env.REMOTE);
98             try {
99                 session = cluster.connect();
100             } finally {
101                 tt.done();
102             }
103             
104             // Load Cred.  We don't follow Visitor, because we have to gather up everything into Identity Anyway
105             Cred.load(trans, session);
106
107             minOwners=1;
108
109             // Create Intermediate Output 
110             writerList = new HashMap<>();
111             
112             expireRange = new ExpireRange(trans.env().access());
113             String sdate = Chrono.dateOnlyStamp(expireRange.now);
114             for( List<Range> lr : expireRange.ranges.values()) {
115                 for(Range r : lr ) {
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();
124                                 deleteCW = cw;
125                         }
126                         trans.init().log("Creating File:",file.getAbsolutePath());
127                         }
128                 }
129             }
130             
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);
137             
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);
144             
145             // Load full data of the following
146             Approval.load(trans, session, Approval.v2_0_17);
147             Role.load(trans, session);
148         } finally {
149             tt0.done();
150         }
151     }
152
153     @Override
154     protected void run(AuthzTrans trans) {
155         AuthzTrans noAvg = trans.env().newTransNoAvg();
156         
157                 ////////////////////
158                 final Map<UUID,Ticket> goodTickets = new TreeMap<>();
159         TimeTaken tt = trans.start("Analyze Expired Futures",Trans.SUB);
160         try {
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);
166                                         if(appls!=null) {
167                                                 for(Approval a : appls) {
168                                                         Approval.row(deleteCW, a);
169                                                 }
170                                         }
171                                 } else if(appls==null) { // Orphaned Future (no Approvals)
172                                         deleteCW.comment("Future is Orphaned");
173                                         Future.row(deleteCW,fut);
174                                 } else  {
175                                         goodTickets.put(fut.fdd.id, new Ticket(fut));
176                                 }
177                         });
178         } finally {
179                 tt.done();
180         }
181                 
182         tt = trans.start("Connect Approvals with Futures",Trans.SUB);
183         try {
184                         for(Approval appr : Approval.list) {
185                                 Ticket ticket=null;
186                                 UUID ticketID = appr.getTicket();
187                                 if(ticketID!=null) {
188                                         ticket = goodTickets.get(appr.getTicket());
189                                 }
190                                 if(ticket == null) { // Orphaned Approvals, no Futures
191                                         deleteCW.comment("Approval is Orphaned");
192                                         Approval.row(deleteCW, appr);
193                                 } else {
194                                         ticket.approvals.add(appr); // add to found Ticket
195                                 }
196                         }
197         } finally {
198                 tt.done();
199         }
200
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 
204                  */
205                 Map<String,Pending> pendingApprs = new HashMap<>();
206                 Map<String,Pending> pendingTemp = new HashMap<>();
207
208                 tt = trans.start("Analyze Good Tickets",Trans.SUB);
209                 try {
210                         for(Ticket ticket : goodTickets.values()) {
211                                 pendingTemp.clear();
212                                 switch(ticket.f.target()) {
213                                         case "user_role":
214                                                 int state[][] = new int[3][3];
215                                                 int type;
216                                                                 
217                                                 for(Approval appr : ticket.approvals) {
218                                                         switch(appr.getType()) {
219                                                                 case "owner":
220                                                                         type=owner;
221                                                                         break;
222                                                                 case "supervisor":
223                                                                         type=supervisor;
224                                                                         break;
225                                                                 default:
226                                                                         type=0;
227                                                         }
228                                                         ++state[type][total]; // count per type
229                                                         switch(appr.getStatus()) {
230                                                                 case "pending":
231                                                                         ++state[type][pending];
232                                                                         Pending n = pendingTemp.get(appr.getApprover());
233                                                                         if(n==null) {
234                                                                                 pendingTemp.put(appr.getApprover(),new Pending(appr.getLast_notified()));
235                                                                         } else {
236                                                                                 n.inc();
237                                                                         }
238                                                                         break;
239                                                                 case "approved":
240                                                                         ++state[type][approved];
241                                                                         break;
242                                                                 default:
243                                                                         ++state[type][unknown];
244                                                         }
245                                                 }
246                                                 
247                                                 // To Approve:
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();
255                                                                 try {
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());
259                                                                         }
260                                                                 } catch (IOException e) {
261                                                                         trans.error().log("Could not reconstitute UserRole");
262                                                                 }
263                                                 } else { // Load all the Pending.
264                                                         for(Entry<String, Pending> es : pendingTemp.entrySet()) {
265                                                                 Pending p = pendingApprs.get(es.getKey());
266                                                                 if(p==null) {
267                                                                         pendingApprs.put(es.getKey(), es.getValue());
268                                                                 } else {
269                                                                         p.inc(es.getValue());
270                                                                 }
271                                                         }
272                                                 }
273                                                 break;
274                                 }
275                         }
276                 } finally {
277                         tt.done();
278                 }
279                 
280                 /**
281                  * Decide to Notify about Approvals, based on activity/last Notified
282                  */
283                 tt = trans.start("Analyze Approval Reminders", Trans.SUB);
284                 try {
285                         GregorianCalendar gc = new GregorianCalendar();
286                         gc.add(GregorianCalendar.DAY_OF_WEEK, 5);
287                         Date remind = gc.getTime();
288                         
289                         for(Entry<String, Pending> es : pendingApprs.entrySet()) {
290                                 Pending p = es.getValue();
291                                 if(p.earliest() == null || p.earliest().after(remind)) {
292                                         p.row(approveCW,es.getKey());
293                                 }
294                         }
295                 } finally {
296                         tt.done();
297                 }
298                 
299                 // clear out Approval Intermediates
300                 goodTickets.clear();
301                 pendingTemp = null;
302                 pendingApprs = null;
303                 
304                 /**
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.
308                 */              
309                 try {
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>>();
313                         try {
314                                 UserRole.load(noAvg, session, UserRole.v2_0_11, ur -> {
315                                         Identity identity;
316                                         try {
317                                                 identity = trans.org().getIdentity(noAvg,ur.user());
318                                                 if(identity==null) {
319                                                         // Candidate for Delete, but not Users if Special
320                                                         String id = ur.user();
321                                                         for(String s : specialDomains) {
322                                                                 if(id.endsWith(s)) {
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);
326                                                                         }
327                                                                         return;
328                                                                 }
329                                                         }
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);
334                                                                 }
335                                                                 return;
336                                                         }
337                                                         ur.row(deleteCW, UserRole.UR,"Not in Organization");
338                                                         return;
339                                                 } else if(Role.byName.get(ur.role())==null) {
340                                                         ur.row(deleteCW, UserRole.UR,String.format("Role %s does not exist", ur.role()));
341                                                         return;
342                                                 }
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());
346                                                         if (urs == null) {
347                                                                 urs = new HashSet<UserRole>();
348                                                                 owners.put(ur.role(), urs);
349                                                         }
350                                                         urs.add(ur);
351                                                 } else {
352                                                         Range r = writeAnalysis(noAvg,ur);
353                                                         if(r!=null) {
354                                                                 Approval existing = findApproval(ur);
355                                                                 if(existing==null) {
356                                                                         ur.row(approveCW,UserRole.APPROVE_UR);
357                                                                 }
358                                                         }
359                                                 }
360                                         } catch (OrganizationException e) {
361                                                 noAvg.error().log(e);
362                                         }
363                                 });
364                         } finally {
365                                 tt.done();
366                         }
367                 
368                         /**
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. 
371                           
372                           Otherwise, write to ExpiredOwners Report
373                         */
374                         tt = trans.start("Analyze Owners Separately",Trans.SUB);
375                         try {
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);
381
382                                         try {
383                                                 for (Set<UserRole> sur : owners.values()) {
384                                                         int goodOwners = 0;
385                                                         for (UserRole ur : sur) {
386                                                                 if (ur.expires().after(expireRange.now)) {
387                                                                         ++goodOwners;
388                                                                 }
389                                                         }
390         
391                                                         for (UserRole ur : sur) {
392                                                                 if (goodOwners >= minOwners) {
393                                                                         Range r = writeAnalysis(noAvg, ur);
394                                                                         if(r!=null) {
395                                                                                 Approval existing = findApproval(ur);
396                                                                                 if(existing==null) {
397                                                                                         ur.row(approveCW,UserRole.APPROVE_UR);
398                                                                                 }
399                                                                         }
400                                                                 } else {
401                                                                         expOwner.row("owner",ur.role(), ur.user(), Chrono.dateOnlyStamp(ur.expires()));
402                                                                         Approval existing = findApproval(ur);
403                                                                         if(existing==null) {
404                                                                                 ur.row(approveCW,UserRole.APPROVE_UR);
405                                                                         }
406                                                                 }
407                                                         }
408                                                 }
409                                         } finally {
410                                                 if(expOwner!=null) {
411                                                         expOwner.close();
412                                                 }
413                                         }
414                                 }
415                         } finally {
416                                 tt.done();
417                         }
418                         
419                         /**
420                          * Check for Expired Credentials
421                          * 
422                          * 
423                          */
424                         tt = trans.start("Analyze Expired Credentials",Trans.SUB);
425                         try {
426                                 for (Cred cred : Cred.data.values()) {
427                                 List<Instance> linst = cred.instances;
428                                 if(linst!=null) {
429                                         Instance lastBath = null;
430                                         for(Instance inst : linst) {
431         //                                      if(inst.attn>0) {
432         //                                              writeAnalysis(trans, cred, inst);
433         //                                              // Special Behavior: only eval the LAST Instance
434         //                                      } else 
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)) {
441                                                                 lastBath = inst;
442                                                         }
443                                                 }
444                                         }
445                                         if(lastBath!=null) {
446                                                 writeAnalysis(noAvg, cred, lastBath);
447                                         }
448                                 }
449                                 }
450                         } finally {
451                                 tt.done();
452                         }
453
454                         ////////////////////
455                         tt = trans.start("Analyze Expired X509s",Trans.SUB);
456                         try {
457                                 X509.load(noAvg, session, x509 -> {
458                                         try {
459                                                 for(Certificate cert : Factory.toX509Certificate(x509.x509)) {
460                                                         writeAnalysis(noAvg, x509, (X509Certificate)cert);
461                                                 }
462                                         } catch (CertificateException | IOException e) {
463                                                 noAvg.error().log(e, "Error Decrypting X509");
464                                         }
465         
466                                 });
467                         } finally {
468                                 tt.done();
469                         }
470                 } catch (FileNotFoundException e) {
471                         noAvg.info().log(e);
472                 }
473         }
474  
475         private Approval findApproval(UserRole ur) {
476                 Approval existing = null;
477                 List<Approval> apprs = Approval.byUser.get(ur.user());
478                 if(apprs!=null) {
479                         for(Approval appr : apprs) {
480                                 if(ur.role().equals(appr.getRole()) &&
481                                         appr.getMemo().contains(Chrono.dateOnlyStamp(ur.expires()))) {
482                                                 existing = appr; 
483                                 }
484                         }
485                 }
486                 return existing;
487         }
488
489         private Range writeAnalysis(AuthzTrans trans, UserRole ur) {
490                 Range r = expireRange.getRange("ur", ur.expires());
491                 if(r!=null) {
492                         CSV.Writer cw = writerList.get(r.name());
493                         if(cw!=null) {
494                                 ur.row(cw,UserRole.UR);
495                         }
496                 }
497                 return r;
498         }
499     
500     private void writeAnalysis(AuthzTrans trans, Cred cred, Instance inst) {
501         if(cred!=null && inst!=null) {
502                         Range r = expireRange.getRange("cred", inst.expires);
503                         if(r!=null) {
504                                 CSV.Writer cw = writerList.get(r.name());
505                                 if(cw!=null) {
506                                         cred.row(cw,inst);
507                                 }
508                         }
509         }
510         }
511
512     private void writeAnalysis(AuthzTrans trans, X509 x509, X509Certificate x509Cert) throws IOException {
513                 Range r = expireRange.getRange("x509", x509Cert.getNotAfter());
514                 if(r!=null) {
515                         CSV.Writer cw = writerList.get(r.name());
516                         if(cw!=null) {
517                                 x509.row(cw,x509Cert);
518                         }
519                 }
520         }
521     
522     @Override
523     protected void _close(AuthzTrans trans) {
524         session.close();
525         for(CSV.Writer cw : writerList.values()) {
526                 cw.close();
527         }
528     }
529
530 }