X-Git-Url: https://gerrit.onap.org/r/gitweb?a=blobdiff_plain;f=auth%2Fauth-certman%2Fsrc%2Fmain%2Fjava%2Forg%2Fonap%2Faaf%2Fauth%2Fcm%2Fservice%2FCMService.java;h=900df8a75c7268fcc8aa25eb3e4ebea482f73991;hb=be1edcb6830745015f5de72e820f40f36dd571ad;hp=89824a471a3bcdbdfbca5bd592e1e5aa7fc7b2d8;hpb=7e966914050e66219689001ff4ab601a49eef0ac;p=aaf%2Fauthz.git diff --git a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java index 89824a47..900df8a7 100644 --- a/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java +++ b/auth/auth-certman/src/main/java/org/onap/aaf/auth/cm/service/CMService.java @@ -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 .certman||"+IGNORE_IPS); + } } public Result requestCert(final AuthzTrans trans, final Result 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 notes = null; - List fqdns = new ArrayList<>(req.value.fqdns); + List 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 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); } } @@ -189,41 +232,49 @@ public class CMService { 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> 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 rc = artiDAO.create(trans, add); // Create new Artifact from Template - if (rc.notOK()) { - return Result.err(rc); + Result 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> 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 certs = Factory.toX509Certificate(cdd.x509); + for(Iterator 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 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> rv = artiDAO.readByMachine(trans, machine); - return rv; + return artiDAO.readByMachine(trans, machine); } public Result> 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"); }