2 * ============LICENSE_START====================================================
4 * ===========================================================================
5 * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6 * ===========================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END====================================================
22 package org.onap.aaf.cadi.cm;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.net.InetAddress;
28 import java.net.UnknownHostException;
29 import java.security.KeyStore;
30 import java.security.cert.X509Certificate;
31 import java.util.ArrayDeque;
32 import java.util.Deque;
33 import java.util.GregorianCalendar;
34 import java.util.HashMap;
35 import java.util.Iterator;
37 import java.util.Map.Entry;
39 import org.onap.aaf.cadi.PropAccess;
40 import org.onap.aaf.cadi.Symm;
41 import org.onap.aaf.cadi.aaf.client.ErrMessage;
42 import org.onap.aaf.cadi.aaf.v2_0.AAFCon;
43 import org.onap.aaf.cadi.aaf.v2_0.AAFConHttp;
44 import org.onap.aaf.cadi.client.Future;
45 import org.onap.aaf.cadi.config.Config;
46 import org.onap.aaf.cadi.http.HBasicAuthSS;
47 import org.onap.aaf.cadi.sso.AAFSSO;
48 import org.onap.aaf.cadi.util.FQI;
49 import org.onap.aaf.misc.env.Env;
50 import org.onap.aaf.misc.env.TimeTaken;
51 import org.onap.aaf.misc.env.Trans;
52 import org.onap.aaf.misc.env.Data.TYPE;
53 import org.onap.aaf.misc.env.util.Chrono;
54 import org.onap.aaf.misc.env.util.Split;
55 import org.onap.aaf.misc.rosetta.env.RosettaDF;
56 import org.onap.aaf.misc.rosetta.env.RosettaEnv;
58 import java.util.Properties;
60 import certman.v1_0.Artifacts;
61 import certman.v1_0.Artifacts.Artifact;
62 import certman.v1_0.CertInfo;
63 import certman.v1_0.CertificateRequest;
65 public class CmAgent {
66 private static final String PRINT = "print";
67 private static final String FILE = "file";
68 private static final String PKCS12 = "pkcs12";
69 private static final String JKS = "jks";
70 private static final String SCRIPT="script";
72 private static final String CM_VER = "1.0";
73 public static final int PASS_SIZE = 24;
74 private static int TIMEOUT;
76 private static RosettaDF<CertificateRequest> reqDF;
77 private static RosettaDF<CertInfo> certDF;
78 private static RosettaDF<Artifacts> artifactsDF;
79 private static ErrMessage errMsg;
80 private static Map<String,PlaceArtifact> placeArtifact;
81 private static RosettaEnv env;
83 private static boolean doExit;
85 public static void main(String[] args) {
89 AAFSSO aafsso = new AAFSSO(args);
90 if(aafsso.loginOnly()) {
91 aafsso.setLogDefault();
93 System.out.println("AAF SSO information created in ~/.aaf");
95 PropAccess access = aafsso.access();
96 env = new RosettaEnv(access.getProperties());
97 Deque<String> cmds = new ArrayDeque<String>();
98 for(String p : args) {
99 if("-noexit".equalsIgnoreCase(p)) {
101 } else if(p.indexOf('=') < 0) {
107 aafsso.setLogDefault();
108 System.out.println("Usage: java -jar <cadi-aaf-*-full.jar> cmd [<tag=value>]*");
109 System.out.println(" create <mechID> [<machine>]");
110 System.out.println(" read <mechID> [<machine>]");
111 System.out.println(" update <mechID> [<machine>]");
112 System.out.println(" delete <mechID> [<machine>]");
113 System.out.println(" copy <mechID> <machine> <newmachine>[,<newmachine>]*");
114 System.out.println(" place <mechID> [<machine>]");
115 System.out.println(" showpass <mechID> [<machine>]");
116 System.out.println(" check <mechID> [<machine>]");
117 System.out.println(" genkeypair");
123 TIMEOUT = Integer.parseInt(env.getProperty(Config.AAF_CONN_TIMEOUT, "5000"));
125 reqDF = env.newDataFactory(CertificateRequest.class);
126 artifactsDF = env.newDataFactory(Artifacts.class);
127 certDF = env.newDataFactory(CertInfo.class);
128 errMsg = new ErrMessage(env);
130 placeArtifact = new HashMap<String,PlaceArtifact>();
131 placeArtifact.put(JKS, new PlaceArtifactInKeystore(JKS));
132 placeArtifact.put(PKCS12, new PlaceArtifactInKeystore(PKCS12));
133 placeArtifact.put(FILE, new PlaceArtifactInFiles());
134 placeArtifact.put(PRINT, new PlaceArtifactOnStream(System.out));
135 placeArtifact.put(SCRIPT, new PlaceArtifactScripts());
137 Trans trans = env.newTrans();
139 if((token=access.getProperty("oauth_token"))!=null) {
140 trans.setProperty("oauth_token", token);
143 // show Std out again
144 aafsso.setLogDefault();
145 aafsso.setStdErrDefault();
147 // if CM_URL can be obtained, add to sso.props, if written
148 String cm_url = getProperty(access,env,false, Config.CM_URL,Config.CM_URL+": ");
150 aafsso.addProp(Config.CM_URL, cm_url);
154 AAFCon<?> aafcon = new AAFConHttp(access,Config.CM_URL);
156 String cmd = cmds.removeFirst();
157 if("place".equals(cmd)) {
158 placeCerts(trans,aafcon,cmds);
159 } else if("create".equals(cmd)) {
160 createArtifact(trans, aafcon,cmds);
161 } else if("read".equals(cmd)) {
162 readArtifact(trans, aafcon, cmds);
163 } else if("copy".equals(cmd)) {
164 copyArtifact(trans, aafcon, cmds);
165 } else if("update".equals(cmd)) {
166 updateArtifact(trans, aafcon, cmds);
167 } else if("delete".equals(cmd)) {
168 deleteArtifact(trans, aafcon, cmds);
169 } else if("showpass".equals(cmd)) {
170 showPass(trans,aafcon,cmds);
171 } else if("check".equals(cmd)) {
173 exitCode = check(trans,aafcon,cmds);
174 } catch (Exception e) {
179 AAFSSO.cons.printf("Unknown command \"%s\"\n", cmd);
182 StringBuilder sb = new StringBuilder();
183 trans.auditTrail(4, sb, Trans.REMOTE);
185 trans.info().log("Trans Info\n",sb);
190 } catch (Exception e) {
193 if(exitCode != 0 && doExit) {
194 System.exit(exitCode);
198 private static String getProperty(PropAccess pa, Env env, boolean secure, String tag, String prompt, Object ... def) {
200 if((value=pa.getProperty(tag))==null) {
202 value = new String(AAFSSO.cons.readPassword(prompt, def));
204 value = AAFSSO.cons.readLine(prompt,def).trim();
207 if(value.length()>0) {
208 pa.setProperty(tag,value);
209 env.setProperty(tag,value);
210 } else if(def.length==1) {
211 value=def[0].toString();
212 pa.setProperty(tag,value);
213 env.setProperty(tag,value);
220 private static String mechID(Deque<String> cmds) {
222 String alias = env.getProperty(Config.CADI_ALIAS);
223 return alias!=null?alias:AAFSSO.cons.readLine("MechID: ");
225 return cmds.removeFirst();
228 private static String machine(Deque<String> cmds) throws UnknownHostException {
230 return cmds.removeFirst();
232 String mach = env.getProperty(Config.HOSTNAME);
233 return mach!=null?mach:InetAddress.getLocalHost().getHostName();
237 private static String[] machines(Deque<String> cmds) {
240 machines = cmds.removeFirst();
242 machines = AAFSSO.cons.readLine("Machines (sep by ','): ");
244 return Split.split(',', machines);
247 private static void createArtifact(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
248 String mechID = mechID(cmds);
249 String machine = machine(cmds);
251 Artifacts artifacts = new Artifacts();
252 Artifact arti = new Artifact();
253 artifacts.getArtifact().add(arti);
254 arti.setMechid(mechID!=null?mechID:AAFSSO.cons.readLine("MechID: "));
255 arti.setMachine(machine!=null?machine:AAFSSO.cons.readLine("Machine (%s): ",InetAddress.getLocalHost().getHostName()));
256 arti.setCa(AAFSSO.cons.readLine("CA: (%s): ","aaf"));
258 String resp = AAFSSO.cons.readLine("Types [file,jks,script] (%s): ", "jks");
259 for(String s : Split.splitTrim(',', resp)) {
260 arti.getType().add(s);
263 if(!resp.contains(SCRIPT)) {
264 arti.getType().add(SCRIPT);
267 // Note: Sponsor is set on Creation by CM
268 String configRootName = FQI.reverseDomain(arti.getMechid());
269 arti.setNs(AAFSSO.cons.readLine("Namespace (%s): ",configRootName));
270 arti.setDir(AAFSSO.cons.readLine("Directory (%s): ", System.getProperty("user.dir")));
271 arti.setOsUser(AAFSSO.cons.readLine("OS User (%s): ", System.getProperty("user.name")));
272 arti.setRenewDays(Integer.parseInt(AAFSSO.cons.readLine("Renewal Days (%s):", "30")));
273 arti.setNotification(toNotification(AAFSSO.cons.readLine("Notification (mailto owner):", "")));
275 TimeTaken tt = trans.start("Create Artifact", Env.REMOTE);
277 Future<Artifacts> future = aafcon.client(CM_VER).create("/cert/artifacts", artifactsDF, artifacts);
278 if(future.get(TIMEOUT)) {
279 trans.info().printf("Call to AAF Certman successful %s, %s",arti.getMechid(), arti.getMachine());
281 trans.error().printf("Call to AAF Certman failed, %s",
282 errMsg.toMsg(future));
289 private static String toNotification(String notification) {
290 if(notification==null) {
292 } else if(notification.length()>0) {
293 if(notification.indexOf(':')<0) {
294 notification = "mailto:" + notification;
301 private static void readArtifact(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
302 String mechID = mechID(cmds);
303 String machine = machine(cmds);
305 TimeTaken tt = trans.start("Read Artifact", Env.SUB);
307 Future<Artifacts> future = aafcon.client(CM_VER)
308 .read("/cert/artifacts/"+mechID+'/'+machine, artifactsDF,"Authorization","Bearer " + trans.getProperty("oauth_token"));
310 if(future.get(TIMEOUT)) {
311 boolean printed = false;
312 for(Artifact a : future.value.getArtifact()) {
313 AAFSSO.cons.printf("MechID: %s\n",a.getMechid());
314 AAFSSO.cons.printf(" Sponsor: %s\n",a.getSponsor());
315 AAFSSO.cons.printf("Machine: %s\n",a.getMachine());
316 AAFSSO.cons.printf("CA: %s\n",a.getCa());
317 StringBuilder sb = new StringBuilder();
318 boolean first = true;
319 for(String t : a.getType()) {
320 if(first) {first=false;}
321 else{sb.append(',');}
324 AAFSSO.cons.printf("Types: %s\n",sb);
325 AAFSSO.cons.printf("Namespace: %s\n",a.getNs());
326 AAFSSO.cons.printf("Directory: %s\n",a.getDir());
327 AAFSSO.cons.printf("O/S User: %s\n",a.getOsUser());
328 AAFSSO.cons.printf("Renew Days: %d\n",a.getRenewDays());
329 AAFSSO.cons.printf("Notification %s\n",a.getNotification());
333 AAFSSO.cons.printf("Artifact for %s %s does not exist\n", mechID, machine);
336 trans.error().log(errMsg.toMsg(future));
343 private static void copyArtifact(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
344 String mechID = mechID(cmds);
345 String machine = machine(cmds);
346 String[] newmachs = machines(cmds);
347 if(machine==null || newmachs == null) {
348 trans.error().log("No machines listed to copy to");
350 TimeTaken tt = trans.start("Copy Artifact", Env.REMOTE);
352 Future<Artifacts> future = aafcon.client(CM_VER)
353 .read("/cert/artifacts/"+mechID+'/'+machine, artifactsDF);
355 if(future.get(TIMEOUT)) {
356 boolean printed = false;
357 for(Artifact a : future.value.getArtifact()) {
358 for(String m : newmachs) {
360 Future<Artifacts> fup = aafcon.client(CM_VER).update("/cert/artifacts", artifactsDF, future.value);
361 if(fup.get(TIMEOUT)) {
362 trans.info().printf("Copy of %s %s successful to %s",mechID,machine,m);
364 trans.error().printf("Call to AAF Certman failed, %s",
372 AAFSSO.cons.printf("Artifact for %s %s does not exist", mechID, machine);
375 trans.error().log(errMsg.toMsg(future));
383 private static void updateArtifact(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
384 String mechID = mechID(cmds);
385 String machine = machine(cmds);
387 TimeTaken tt = trans.start("Update Artifact", Env.REMOTE);
389 Future<Artifacts> fread = aafcon.client(CM_VER)
390 .read("/cert/artifacts/"+mechID+'/'+machine, artifactsDF);
392 if(fread.get(TIMEOUT)) {
393 Artifacts artifacts = new Artifacts();
394 for(Artifact a : fread.value.getArtifact()) {
395 Artifact arti = new Artifact();
396 artifacts.getArtifact().add(arti);
398 AAFSSO.cons.printf("For %s on %s\n", a.getMechid(),a.getMachine());
399 arti.setMechid(a.getMechid());
400 arti.setMachine(a.getMachine());
401 arti.setCa(AAFSSO.cons.readLine("CA: (%s): ",a.getCa()));
402 StringBuilder sb = new StringBuilder();
403 boolean first = true;
404 for(String t : a.getType()) {
405 if(first) {first=false;}
406 else{sb.append(',');}
410 String resp = AAFSSO.cons.readLine("Types [file,jks,pkcs12] (%s): ", sb);
411 for(String s : Split.splitTrim(',', resp)) {
412 arti.getType().add(s);
415 if(!resp.contains(SCRIPT)) {
416 arti.getType().add(SCRIPT);
419 // Note: Sponsor is set on Creation by CM
420 arti.setNs(AAFSSO.cons.readLine("Namespace (%s): ",a.getNs()));
421 arti.setDir(AAFSSO.cons.readLine("Directory (%s): ", a.getDir()));
422 arti.setOsUser(AAFSSO.cons.readLine("OS User (%s): ", a.getOsUser()));
423 arti.setRenewDays(Integer.parseInt(AAFSSO.cons.readLine("Renew Days (%s):", a.getRenewDays())));
424 arti.setNotification(toNotification(AAFSSO.cons.readLine("Notification (%s):", a.getNotification())));
427 if(artifacts.getArtifact().size()==0) {
428 AAFSSO.cons.printf("Artifact for %s %s does not exist", mechID, machine);
430 Future<Artifacts> fup = aafcon.client(CM_VER).update("/cert/artifacts", artifactsDF, artifacts);
431 if(fup.get(TIMEOUT)) {
432 trans.info().printf("Call to AAF Certman successful %s, %s",mechID,machine);
434 trans.error().printf("Call to AAF Certman failed, %s",
439 trans.error().printf("Call to AAF Certman failed, %s %s, %s",
440 errMsg.toMsg(fread),mechID,machine);
447 private static void deleteArtifact(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
448 String mechid = mechID(cmds);
449 String machine = machine(cmds);
451 TimeTaken tt = trans.start("Delete Artifact", Env.REMOTE);
453 Future<Void> future = aafcon.client(CM_VER)
454 .delete("/cert/artifacts/"+mechid+"/"+machine,"application/json" );
456 if(future.get(TIMEOUT)) {
457 trans.info().printf("Call to AAF Certman successful %s, %s",mechid,machine);
459 trans.error().printf("Call to AAF Certman failed, %s %s, %s",
460 errMsg.toMsg(future),mechid,machine);
469 private static boolean placeCerts(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
471 String mechID = mechID(cmds);
472 String machine = machine(cmds);
473 String[] fqdns = Split.split(':', machine);
482 TimeTaken tt = trans.start("Place Artifact", Env.REMOTE);
484 Future<Artifacts> acf = aafcon.client(CM_VER)
485 .read("/cert/artifacts/"+mechID+'/'+key, artifactsDF);
486 if(acf.get(TIMEOUT)) {
487 if(acf.value.getArtifact()==null || acf.value.getArtifact().isEmpty()) {
488 AAFSSO.cons.printf("===> There are no artifacts for %s on machine '%s'\n", mechID, key);
490 for(Artifact a : acf.value.getArtifact()) {
491 String osID = System.getProperty("user.name");
492 if(a.getOsUser().equals(osID)) {
493 CertificateRequest cr = new CertificateRequest();
494 cr.setMechid(a.getMechid());
495 cr.setSponsor(a.getSponsor());
496 for(int i=0;i<fqdns.length;++i) {
497 cr.getFqdns().add(fqdns[i]);
499 Future<String> f = aafcon.client(CM_VER)
500 .setQueryParams("withTrust")
501 .updateRespondString("/cert/" + a.getCa(),reqDF, cr);
503 CertInfo capi = certDF.newData().in(TYPE.JSON).load(f.body()).asObject();
504 for(String type : a.getType()) {
505 PlaceArtifact pa = placeArtifact.get(type);
507 if(rv = pa.place(trans, capi, a,machine)) {
512 // Cover for the above multiple pass possibilities with some static Data, then clear per Artifact
514 trans.error().log(errMsg.toMsg(f));
517 trans.error().log("You must be OS User \"" + a.getOsUser() +"\" to place Certificates on this box");
522 trans.error().log(errMsg.toMsg(acf));
530 private static void notifyPlaced(Artifact a, boolean rv) {
533 private static void showPass(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
534 String mechID = mechID(cmds);
535 String machine = machine(cmds);
537 TimeTaken tt = trans.start("Show Password", Env.REMOTE);
539 Future<Artifacts> acf = aafcon.client(CM_VER)
540 .read("/cert/artifacts/"+mechID+'/'+machine, artifactsDF);
541 if(acf.get(TIMEOUT)) {
542 // Have to wait for JDK 1.7 source...
543 //switch(artifact.getType()) {
544 if(acf.value.getArtifact()==null || acf.value.getArtifact().isEmpty()) {
545 AAFSSO.cons.printf("No Artifacts found for %s on %s", mechID, machine);
547 String id = aafcon.defID();
549 for(Artifact a : acf.value.getArtifact()) {
550 allowed = id!=null && (id.equals(a.getSponsor()) ||
551 (id.equals(a.getMechid())
552 && aafcon.securityInfo().defSS.getClass().isAssignableFrom(HBasicAuthSS.class)));
554 Future<String> pf = aafcon.client(CM_VER).read("/cert/may/" +
555 a.getNs() + ".certman|"+a.getCa()+"|showpass","*/*");
556 if(pf.get(TIMEOUT)) {
559 trans.error().log(errMsg.toMsg(pf));
563 File dir = new File(a.getDir());
564 Properties props = new Properties();
565 FileInputStream fis = new FileInputStream(new File(dir,a.getNs()+".props"));
569 fis = new FileInputStream(new File(dir,a.getNs()+".chal"));
575 File f = new File(dir,a.getNs()+".keyfile");
577 Symm symm = Symm.obtain(f);
579 for(Iterator<Entry<Object,Object>> iter = props.entrySet().iterator(); iter.hasNext();) {
580 Entry<Object,Object> en = iter.next();
581 if(en.getValue().toString().startsWith("enc:")) {
582 System.out.printf("%s=%s\n", en.getKey(), symm.depass(en.getValue().toString()));
586 trans.error().printf("%s.keyfile must exist to read passwords for %s on %s",
587 f.getAbsolutePath(),a.getMechid(), a.getMachine());
593 trans.error().log(errMsg.toMsg(acf));
603 * Check returns Error Codes, so that Scripts can know what to do
605 * 0 - Check Complete, nothing to do
607 * 2 - Error for specific Artifact - read check.msg
608 * 10 - Certificate Updated - check.msg is email content
616 private static int check(Trans trans, AAFCon<?> aafcon, Deque<String> cmds) throws Exception {
618 String mechID = mechID(cmds);
619 String machine = machine(cmds);
621 TimeTaken tt = trans.start("Check Certificate", Env.REMOTE);
624 Future<Artifacts> acf = aafcon.client(CM_VER)
625 .read("/cert/artifacts/"+mechID+'/'+machine, artifactsDF);
626 if(acf.get(TIMEOUT)) {
627 // Have to wait for JDK 1.7 source...
628 //switch(artifact.getType()) {
629 if(acf.value.getArtifact()==null || acf.value.getArtifact().isEmpty()) {
630 AAFSSO.cons.printf("No Artifacts found for %s on %s", mechID, machine);
632 String id = aafcon.defID();
633 GregorianCalendar now = new GregorianCalendar();
634 for(Artifact a : acf.value.getArtifact()) {
635 if(id.equals(a.getMechid())) {
636 File dir = new File(a.getDir());
637 Properties props = new Properties();
638 FileInputStream fis = new FileInputStream(new File(dir,a.getNs()+".props"));
648 if((prop=props.getProperty(Config.CADI_KEYFILE))==null ||
649 !(f=new File(prop)).exists()) {
650 trans.error().printf("Keyfile must exist to check Certificates for %s on %s",
651 a.getMechid(), a.getMachine());
653 String ksf = props.getProperty(Config.CADI_KEYSTORE);
654 String ksps = props.getProperty(Config.CADI_KEYSTORE_PASSWORD);
655 if(ksf==null || ksps == null) {
656 trans.error().printf("Properties %s and %s must exist to check Certificates for %s on %s",
657 Config.CADI_KEYSTORE, Config.CADI_KEYSTORE_PASSWORD,a.getMechid(), a.getMachine());
659 KeyStore ks = KeyStore.getInstance("JKS");
660 Symm symm = Symm.obtain(f);
662 fis = new FileInputStream(ksf);
664 ks.load(fis,symm.depass(ksps).toCharArray());
668 X509Certificate cert = (X509Certificate)ks.getCertificate(mechID);
672 msg = String.format("X509Certificate does not exist for %s on %s in %s",
673 a.getMechid(), a.getMachine(), ksf);
674 trans.error().log(msg);
677 GregorianCalendar renew = new GregorianCalendar();
678 renew.setTime(cert.getNotAfter());
679 renew.add(GregorianCalendar.DAY_OF_MONTH,-1*a.getRenewDays());
680 if(renew.after(now)) {
681 msg = String.format("X509Certificate for %s on %s has been checked on %s. It expires on %s; it will not be renewed until %s.\n",
682 a.getMechid(), a.getMachine(),Chrono.dateOnlyStamp(now),cert.getNotAfter(),Chrono.dateOnlyStamp(renew));
683 trans.info().log(msg);
686 trans.info().printf("X509Certificate for %s on %s expiration, %s, needs Renewal.\n",
687 a.getMechid(), a.getMachine(),cert.getNotAfter());
688 cmds.offerLast(mechID);
689 cmds.offerLast(machine);
690 if(placeCerts(trans,aafcon,cmds)) {
691 msg = String.format("X509Certificate for %s on %s has been renewed. Ensure services using are refreshed.\n",
692 a.getMechid(), a.getMachine());
693 exitCode = 10; // Refreshed
695 msg = String.format("X509Certificate for %s on %s attempted renewal, but failed. Immediate Investigation is required!\n",
696 a.getMechid(), a.getMachine());
697 exitCode = 1; // Error Renewing
702 FileOutputStream fos = new FileOutputStream(a.getDir()+'/'+a.getNs()+".msg");
704 fos.write(msg.getBytes());
716 trans.error().log(errMsg.toMsg(acf));