Fix Agent and CM Issues
[aaf/authz.git] / auth / auth-certman / src / main / java / org / onap / aaf / auth / cm / service / CMService.java
index 62f0d68..900df8a 100644 (file)
@@ -3,13 +3,14 @@
  * org.onap.aaf
  * ===========================================================================
  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019 IBM.
  * ===========================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *      http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -26,10 +27,13 @@ import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
 import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -43,6 +47,7 @@ import org.onap.aaf.auth.cm.data.CertRenew;
 import org.onap.aaf.auth.cm.data.CertReq;
 import org.onap.aaf.auth.cm.data.CertResp;
 import org.onap.aaf.auth.cm.validation.CertmanValidator;
+import org.onap.aaf.auth.common.Define;
 import org.onap.aaf.auth.dao.CassAccess;
 import org.onap.aaf.auth.dao.cass.ArtiDAO;
 import org.onap.aaf.auth.dao.cass.CacheInfoDAO;
@@ -57,6 +62,7 @@ import org.onap.aaf.auth.layer.Result;
 import org.onap.aaf.auth.org.Organization;
 import org.onap.aaf.auth.org.Organization.Identity;
 import org.onap.aaf.auth.org.OrganizationException;
+import org.onap.aaf.cadi.Access.Level;
 import org.onap.aaf.cadi.Hash;
 import org.onap.aaf.cadi.Permission;
 import org.onap.aaf.cadi.aaf.AAFPermission;
@@ -71,22 +77,31 @@ public class CMService {
     private static final int STD_RENEWAL = 30;
     private static final int MAX_RENEWAL = 60;
     private static final int MIN_RENEWAL = 10;
+    // Limit total requests
+    private static final int MAX_X509s = 200; // Need a "LIMIT Exception" DB.
+    private static final String MAX_X509S_TAG = "cm_max_x509s"; // be able to adjust limit in future
 
     public static final String REQUEST = "request";
     public static final String IGNORE_IPS = "ignoreIPs";
     public static final String RENEW = "renew";
     public static final String DROP = "drop";
     public static final String DOMAIN = "domain";
+    public static final String DYNAMIC_SANS="dynamic_sans";
 
     private static final String CERTMAN = "certman";
     private static final String ACCESS = "access";
 
     private static final String[] NO_NOTES = new String[0];
     private final Permission root_read_permission;
+       private final String aaf_ns;
+
     private final CertDAO certDAO;
     private final CredDAO credDAO;
     private final ArtiDAO artiDAO;
-    private AAF_CM certman;
+    private AAF_CM certManager;
+    private Boolean allowIgnoreIPs;
+    private AAFPermission limitOverridePerm;
+    private int max_509s;
 
     // @SuppressWarnings("unchecked")
     public CMService(final AuthzTrans trans, AAF_CM certman) throws APIException, IOException {
@@ -99,19 +114,30 @@ public class CMService {
         credDAO = new CredDAO(trans, hd, cid);
         artiDAO = new ArtiDAO(trans, hd, cid);
 
-        this.certman = certman;
-        
+        this.certManager = certman;
+
+        aaf_ns = trans.getProperty(Config.AAF_ROOT_NS, Config.AAF_ROOT_NS_DEF);
         root_read_permission=new AAFPermission(
-            trans.getProperty(Config.AAF_ROOT_NS, Config.AAF_ROOT_NS_DEF),
-            "access",
-            "*",
-            "read"
+                aaf_ns,
+                ACCESS,
+                "*",
+                "read"
         );
+        try {
+            max_509s = Integer.parseInt(trans.env().getProperty(MAX_X509S_TAG,Integer.toString(MAX_X509s)));
+        } catch (Exception e) {
+            trans.env().log(e, "");
+            max_509s = MAX_X509s;
+        }
+        limitOverridePerm = new AAFPermission(Define.ROOT_NS(),"certman","quantity","override");
+        allowIgnoreIPs = Boolean.valueOf(certman.access.getProperty(Config.CM_ALLOW_IGNORE_IPS, "false"));
+        if(allowIgnoreIPs) {
+            trans.env().access().log(Level.INIT, "Allowing DNS Evaluation to be turned off with <ns>.certman|<ca name>|"+IGNORE_IPS);
+        }
     }
 
     public Result<CertResp> requestCert(final AuthzTrans trans, final Result<CertReq> req, final CA ca) {
         if (req.isOK()) {
-
             if (req.value.fqdns.isEmpty()) {
                 return Result.err(Result.ERR_BadData, "No Machines passed in Request");
             }
@@ -125,52 +151,68 @@ public class CMService {
             }
 
             List<String> notes = null;
-            List<String> fqdns = new ArrayList<>(req.value.fqdns);
+            List<String> fqdns;
+            boolean dynamic_sans = trans.fish(new AAFPermission(null, ca.getPermType(), ca.getName(),DYNAMIC_SANS));
+            boolean ignoreIPs = trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), IGNORE_IPS));
+            boolean domain_based = false;
+
+            // Note: Many Cert Impls require FQDN in "CN=" to be in the SANS as well.  Therefore, the "fqdn" variable
+            // includes main ID plus ADDITIONAL SANS at all times.
+            if(req.value.fqdns.isEmpty()) {
+                fqdns = new ArrayList<>();
+                fqdns.add(key);
+            } else {
+                // Only Template or Dynamic permitted to pass in FQDNs
+                if (req.value.fqdns.get(0).startsWith("*")) { // Domain set
+                    if (trans.fish(new AAFPermission(null,ca.getPermType(), ca.getName(), DOMAIN))) {
+                        domain_based = true;
+                    } else {
+                        return Result.err(Result.ERR_Denied,
+                              "Domain based Authorizations (" + req.value.fqdns.get(0) + ") requires Exception");
+                    }
+                }
+                fqdns = new ArrayList<>(req.value.fqdns);
+            }
 
             String email = null;
 
             try {
                 Organization org = trans.org();
-
-                boolean ignoreIPs = trans.fish(new AAFPermission(mechNS,CERTMAN, ca.getName(), IGNORE_IPS));
-
                 InetAddress primary = null;
                 // Organize incoming information to get to appropriate Artifact
-                if (!fqdns.isEmpty()) {
+                if (!fqdns.isEmpty()) { // Passed in FQDNS, validated above
                     // Accept domain wild cards, but turn into real machines
                     // Need *domain.com:real.machine.domain.com:san.machine.domain.com:...
-                    if (fqdns.get(0).startsWith("*")) { // Domain set
-                        if (!trans.fish(new AAFPermission(null,ca.getPermType(), ca.getName(), DOMAIN))) {
-                            return Result.err(Result.ERR_Denied,
-                                    "Domain based Authorizations (" + fqdns.get(0) + ") requires Exception");
-                        }
-
-                        // TODO check for Permission in Add Artifact?
-                        String domain = fqdns.get(0).substring(1);
+                    if (domain_based) { // Domain set
+                        // check for Permission in Add Artifact?
+                        String domain = fqdns.get(0).substring(1); // starts with *, see above
                         fqdns.remove(0);
                         if (fqdns.isEmpty()) {
-                            return Result.err(Result.ERR_Denied, "Requests using domain require machine declaration");
+                            return Result.err(Result.ERR_Denied,
+                                "Requests using domain require machine declaration");
                         }
 
                         if (!ignoreIPs) {
                             InetAddress ia = InetAddress.getByName(fqdns.get(0));
                             if (ia == null) {
                                 return Result.err(Result.ERR_Denied,
-                                        "Request not made from matching IP matching domain");
+                                     "Request not made from matching IP matching domain");
                             } else if (ia.getHostName().endsWith(domain)) {
                                 primary = ia;
                             }
                         }
 
                     } else {
-                        for (String cn : req.value.fqdns) {
-                            if(!ignoreIPs) {
+                        // Passed in FQDNs, but not starting with *
+                        if (!ignoreIPs) {
+                            for (String cn : req.value.fqdns) {
                                 try {
                                     InetAddress[] ias = InetAddress.getAllByName(cn);
                                     Set<String> potentialSanNames = new HashSet<>();
                                     for (InetAddress ia1 : ias) {
                                         InetAddress ia2 = InetAddress.getByAddress(ia1.getAddress());
-                                        if (primary == null && ias.length == 1 && trans.ip().equals(ia1.getHostAddress())) {
+                                        String ip = trans.ip();
+                                        if (primary == null && ip.equals(ia1.getHostAddress())) {
                                             primary = ia1;
                                         } else if (!cn.equals(ia1.getHostName())
                                                 && !ia2.getHostName().equals(ia2.getHostAddress())) {
@@ -178,6 +220,7 @@ public class CMService {
                                         }
                                     }
                                 } catch (UnknownHostException e1) {
+                                    trans.debug().log(e1);
                                     return Result.err(Result.ERR_BadData, "There is no DNS lookup for %s", cn);
                                 }
                             }
@@ -186,44 +229,52 @@ public class CMService {
                 }
 
                 final String host;
-                if(ignoreIPs) {
+                if (ignoreIPs) {
                     host = req.value.fqdns.get(0);
                 } else if (primary == null) {
-                    return Result.err(Result.ERR_Denied, "Request not made from matching IP (%s)", trans.ip());
+                    return Result.err(Result.ERR_Denied, "Request not made from matching IP (%s)", req.value.fqdns.get(0));
                 } else {
-                    host = primary.getHostAddress();
+                    String thost = primary.getHostName();
+                    host = thost==null?primary.getHostAddress():thost;
                 }
 
                 ArtiDAO.Data add = null;
                 Result<List<ArtiDAO.Data>> ra = artiDAO.read(trans, req.value.mechid, host);
                 if (ra.isOKhasData()) {
-                    if (add == null) {
-                        add = ra.value.get(0); // single key
+                    add = ra.value.get(0); // single key
+                    if(dynamic_sans && (add.sans!=null && !add.sans.isEmpty())) { // do not allow both Dynamic and Artifact SANS
+                        return Result.err(Result.ERR_Denied,"Authorization must not include SANS when doing Dynamic SANS (%s, %s)", req.value.mechid, key);
                     }
                 } else {
-                    ra = artiDAO.read(trans, req.value.mechid, key);
-                    if (ra.isOKhasData()) { // is the Template available?
-                        add = ra.value.get(0);
-                        add.machine = host;
-                        for (String s : fqdns) {
-                            if (!s.equals(add.machine)) {
-                                add.sans(true).add(s);
+                    if(domain_based) {
+                        ra = artiDAO.read(trans, req.value.mechid, key);
+                        if (ra.isOKhasData()) { // is the Template available?
+                            add = ra.value.get(0);
+                            add.machine = host;
+                            for (String s : fqdns) {
+                                if (!s.equals(add.machine)) {
+                                    add.sans(true).add(s);
+                                }
                             }
-                        }
-                        Result<ArtiDAO.Data> rc = artiDAO.create(trans, add); // Create new Artifact from Template
-                        if (rc.notOK()) {
-                            return Result.err(rc);
+                            Result<ArtiDAO.Data> rc = artiDAO.create(trans, add); // Create new Artifact from Template
+                            if (rc.notOK()) {
+                                return Result.err(rc);
+                            }
+                        } else {
+                            return Result.err(Result.ERR_Denied,"No Authorization Template for %s, %s", req.value.mechid, key);
                         }
                     } else {
-                        add = ra.value.get(0);
+                        return Result.err(Result.ERR_Denied,"No Authorization found for %s, %s", req.value.mechid, key);
                     }
                 }
 
                 // Add Artifact listed FQDNs
-                if (add.sans != null) {
-                    for (String s : add.sans) {
-                        if (!fqdns.contains(s)) {
-                            fqdns.add(s);
+                if(!dynamic_sans) {
+                    if (add.sans != null) {
+                        for (String s : add.sans) {
+                            if (!fqdns.contains(s)) {
+                                fqdns.add(s);
+                            }
                         }
                     }
                 }
@@ -237,16 +288,16 @@ public class CMService {
 
                 // Policy 3: MechID must be current
                 Identity muser = org.getIdentity(trans, add.mechid);
-                if (muser == null) {
-                    return Result.err(Result.ERR_Policy, "MechID must exist in %s", org.getName());
+                if (muser == null || !muser.isFound()) {
+                    return Result.err(Result.ERR_Policy, "AppID '%s' must exist in %s",add.mechid,org.getName());
                 }
 
                 // Policy 4: Sponsor must be current
                 Identity ouser = muser.responsibleTo();
-                if (ouser == null) {
+                if (ouser == null || !ouser.isFound()) {
                     return Result.err(Result.ERR_Policy, "%s does not have a current sponsor at %s", add.mechid,
                             org.getName());
-                } else if (!ouser.isFound() || ouser.mayOwn() != null) {
+                } else if (ouser.mayOwn() != null) {
                     return Result.err(Result.ERR_Policy, "%s reports that %s cannot be responsible for %s",
                             org.getName(), trans.user());
                 }
@@ -268,25 +319,33 @@ public class CMService {
                             trans.user(), mechNS);
                 }
 
+                // Policy 8: IP Addresses allowed in Certs only by Permission
+                if(!trans.fish(new AAFPermission(aaf_ns,CERTMAN, ca.getName(), "ip"))) {
+                       for(String fqdn : fqdns) {
+                       if(CA.IPV4_PATTERN.matcher(fqdn).matches() || CA.IPV6_PATTERN.matcher(fqdn).matches()) {
+                            return Result.err(Status.ERR_Denied,
+                                    "Machines include a IP Address.  IP Addresses are not allowed except by Permission");
+                       }
+                       }
+                }
+
                 // Make sure Primary is the first in fqdns
+
                 if (fqdns.size() > 1) {
                     for (int i = 0; i < fqdns.size(); ++i) {
-                        if(primary==null) {
+                        if (primary==null && !ignoreIPs) {
                             trans.error().log("CMService var primary is null");
                         } else {
                             String fg = fqdns.get(i);
-                            if (fg!=null && fg.equals(primary.getHostName())) {
-                                if (i != 0) {
+                            if ((fg!=null && primary!=null && fg.equals(primary.getHostName()))&&(i != 0)) {
                                     String tmp = fqdns.get(0);
                                     fqdns.set(0, primary.getHostName());
                                     fqdns.set(i, tmp);
-                                }
                             }
                         }
                     }
                 }
             } catch (Exception e) {
-                e.printStackTrace();
                 trans.error().log(e);
                 return Result.err(Status.ERR_Denied,
                         "AppID Sponsorship cannot be determined at this time.  Try later.");
@@ -295,6 +354,30 @@ public class CMService {
             CSRMeta csrMeta;
             try {
                 csrMeta = BCFactory.createCSRMeta(ca, req.value.mechid, email, fqdns);
+                csrMeta.environment(ca.getEnv());
+
+                // Before creating, make sure they don't have too many
+                if(!trans.fish(limitOverridePerm)) {
+                    Result<List<CertDAO.Data>> existing = certDAO.readID(trans, req.value.mechid);
+                    if(existing.isOK()) {
+                        String cn = "CN=" + csrMeta.cn();
+                        int count = 0;
+                        Date now = new Date();
+                        for (CertDAO.Data cdd : existing.value) {
+                            Collection<? extends Certificate> certs = Factory.toX509Certificate(cdd.x509);
+                            for(Iterator<? extends Certificate> iter = certs.iterator(); iter.hasNext();) {
+                                X509Certificate x509 = (X509Certificate)iter.next();
+                                if((x509.getNotAfter().after(now) && x509.getSubjectDN().getName().contains(cn))&&(++count>max_509s)) {
+                                        break;
+                                     }
+                            }
+                        }
+                        if(count>max_509s) {
+                            return Result.err(Result.ERR_Denied, "There are too many Certificates generated for " + cn + " for " + req.value.mechid);
+                        }
+                    }
+                }
+                // Here is where we send off to CA for Signing.
                 X509andChain x509ac = ca.sign(trans, csrMeta);
                 if (x509ac == null) {
                     return Result.err(Result.ERR_ActionNotCompleted, "x509 Certificate not signed by CA");
@@ -308,6 +391,7 @@ public class CMService {
                 cdd.id = req.value.mechid;
                 cdd.x500 = x509.getSubjectDN().getName();
                 cdd.x509 = Factory.toString(trans, x509);
+
                 certDAO.create(trans, cdd);
 
                 CredDAO.Data crdd = new CredDAO.Data();
@@ -317,12 +401,13 @@ public class CMService {
                 crdd.id = req.value.mechid;
                 crdd.ns = Question.domain2ns(crdd.id);
                 crdd.type = CredDAO.CERT_SHA256_RSA;
+                crdd.tag = cdd.ca + '|' + cdd.serial.toString();
                 credDAO.create(trans, crdd);
 
                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(notes));
                 return Result.ok(cr);
             } catch (Exception e) {
-                trans.error().log(e);
+                trans.debug().log(e);
                 return Result.err(Result.ERR_ActionNotCompleted, e.getMessage());
             }
         } else {
@@ -371,7 +456,7 @@ public class CMService {
             try {
                 ouser = org.getIdentity(trans, trans.user());
             } catch (OrganizationException e1) {
-                trans.error().log(e1);
+                trans.debug().log(e1);
                 ouser = null;
             }
             if (ouser == null) {
@@ -399,7 +484,7 @@ public class CMService {
                 CertResp cr = new CertResp(trans, ca, x509, csrMeta, x509ac.getTrustChain(), compileNotes(null));
                 return Result.ok(cr);
             } catch (Exception e) {
-                trans.error().log(e);
+                trans.debug().log(e);
                 return Result.err(Result.ERR_ActionNotCompleted, e.getMessage());
             }
         } else {
@@ -457,8 +542,7 @@ public class CMService {
 
                 // Policy 6: Only do Domain by Exception
                 if (add.machine.startsWith("*")) { // Domain set
-                    CA ca = certman.getCA(add.ca);
-
+                    CA ca = certManager.getCA(add.ca);
                     if (!trans.fish(new AAFPermission(ca.getPermNS(),ca.getPermType(), add.ca, DOMAIN))) {
                         return Result.err(Result.ERR_Denied, "Domain Artifacts (%s) requires specific Permission",
                                 add.machine);
@@ -473,7 +557,7 @@ public class CMService {
             }
             // Add to DB
             Result<ArtiDAO.Data> rv = artiDAO.create(trans, add);
-            // TODO come up with Partial Reporting Scheme, or allow only one at a time.
+            //  come up with Partial Reporting Scheme, or allow only one at a time.
             if (rv.notOK()) {
                 return Result.err(rv);
             }
@@ -491,18 +575,18 @@ public class CMService {
             return data;
         }
         add = data.value.get(0);
-        if (trans.user().equals(add.mechid) 
+        if (trans.user().equals(add.mechid)
                 || trans.fish(root_read_permission,
-                              new AAFPermission(add.ns,ACCESS, "*", "read"),
-                              new AAFPermission(add.ns,CERTMAN, add.ca, "read"),
-                              new AAFPermission(add.ns,CERTMAN, add.ca, "request"))
+                new AAFPermission(add.ns,ACCESS, "*", "read"),
+                new AAFPermission(add.ns,CERTMAN, add.ca, "read"),
+                new AAFPermission(add.ns,CERTMAN, add.ca, REQUEST))
                 || (trans.org().validate(trans, Organization.Policy.OWNS_MECHID, null, add.mechid)) == null) {
             return data;
         } else {
             return Result.err(Result.ERR_Denied,
                     "%s is not %s, is not the sponsor, and doesn't have delegated permission.", trans.user(),
                     add.mechid, add.ns + ".certman|" + add.ca + "|read or ...|request"); // note: reason is set by 2nd
-                                                                                            // case, if 1st case misses
+            // case, if 1st case misses
         }
 
     }
@@ -533,10 +617,9 @@ public class CMService {
             return Result.err(Result.ERR_BadData, v.errs());
         }
 
-        // TODO do some checks?
+        //  do some checks?
 
-        Result<List<ArtiDAO.Data>> rv = artiDAO.readByMachine(trans, machine);
-        return rv;
+        return artiDAO.readByMachine(trans, machine);
     }
 
     public Result<List<ArtiDAO.Data>> readArtifactsByNs(AuthzTrans trans, String ns) {
@@ -546,8 +629,7 @@ public class CMService {
             return Result.err(Result.ERR_BadData, v.errs());
         }
 
-        // TODO do some checks?
-
+        //  do some checks?
         return artiDAO.readByNs(trans, ns);
     }
 
@@ -559,7 +641,7 @@ public class CMService {
         }
 
         // Check if requesting User is Sponsor
-        // TODO - Shall we do one, or multiples?
+        //  Shall we do one, or multiples?
         for (ArtiDAO.Data add : list) {
             // Policy 1: MechID must exist in Org
             Identity muser = trans.org().getIdentity(trans, add.mechid);
@@ -592,7 +674,7 @@ public class CMService {
             }
             // Policy 6: Only do Domain by Exception
             if (add.machine.startsWith("*")) { // Domain set
-                CA ca = certman.getCA(add.ca);
+                CA ca = certManager.getCA(add.ca);
                 if (ca == null) {
                     return Result.err(Result.ERR_BadData, "CA is required in Artifact");
                 }