diff --git a/blended.security.scep/src/main/scala/blended/security/scep/internal/ScepCertificateProvider.scala b/blended.security.scep/src/main/scala/blended/security/scep/internal/ScepCertificateProvider.scala index b9d8fe99b..c6fb884cc 100644 --- a/blended.security.scep/src/main/scala/blended/security/scep/internal/ScepCertificateProvider.scala +++ b/blended.security.scep/src/main/scala/blended/security/scep/internal/ScepCertificateProvider.scala @@ -1,12 +1,14 @@ package blended.security.scep.internal +import java.io.{ByteArrayOutputStream, PrintStream} import java.net.URL -import java.security.cert.Certificate +import java.security._ +import java.security.cert.{Certificate, X509Certificate} import blended.security.ssl._ import javax.security.auth.callback.CallbackHandler import javax.security.auth.x500.X500Principal -import org.bouncycastle.asn1.DERPrintableString +import org.bouncycastle.asn1.{ASN1Encodable, DERPrintableString} import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder @@ -17,6 +19,9 @@ import org.jscep.transport.response.Capabilities import scala.collection.JavaConverters._ import scala.util.Try import blended.util.logging.Logger +import org.bouncycastle.asn1.x509.{Extension, ExtensionsGenerator, GeneralName, GeneralNames} +import org.bouncycastle.pkcs.PKCS10CertificationRequest +import sun.misc.BASE64Encoder case class ScepConfig( url : String, @@ -26,7 +31,9 @@ case class ScepConfig( scepChallenge : String ) -class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider { +class ScepCertificateProvider(cfg: ScepConfig) + extends CertificateRequestBuilder + with CertificateProvider { private[this] lazy val log = Logger[ScepCertificateProvider] @@ -47,10 +54,10 @@ class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider { existing match { case None => log.info("Obtaining initial server certificate from SCEP server.") - enroll(selfSignedCertificate(cnProvider).get, cnProvider) + enroll(None, cnProvider) case Some(c) => log.info("Refreshing certificate previously obtained from SCEP server.") - enroll(c, cnProvider) + enroll(Some(c), cnProvider) } } @@ -66,27 +73,64 @@ class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider { new SelfSignedCertificateProvider(selfSignedConfig).refreshCertificate(None, cnProvider) } - private[this] def enroll(inCert : CertificateHolder, cnProvider: CommonNameProvider): Try[CertificateHolder] = Try { + private def dumpCsr(csr : PKCS10CertificationRequest) : Unit = { - inCert.privateKey match { + val s = new BASE64Encoder().encode(csr.getEncoded()) + println("-----BEGIN CERTIFICATE REQUEST-----\n" + s + "\n-----END CERTIFICATE REQUEST-----\n") + } + + private[this] def enroll( + inCert : Option[CertificateHolder], + cnProvider: CommonNameProvider + ): Try[CertificateHolder] = Try { + + val selfSigned = selfSignedCertificate(cnProvider).get + + val csrSignKey : Option[PrivateKey] = inCert match { + case None => selfSigned.privateKey + case Some(c) => c.privateKey + } + + csrSignKey match { case None => - throw new Exception("Certificate to refresh must have a private key defined.") + throw new Exception("No key found to sign SCEP certificate request.") case Some(privKey) => - val reqCert = inCert.chain.head - - log.info(s"Trying to obtain server certificate from SCEP server at [${cfg.url}] with existing certificate [${X509CertificateInfo(reqCert)}]" ) + val reqCert : X509Certificate = inCert match { + case None => + log.info(s"Requesting initial certificate from SCEP server at [${cfg.url}].") + selfSigned.chain.head + case Some(c) => + log.info(s"Refreshing certificate from SCEP server at [${cfg.url}].") + c.chain.head + } - val csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(cnProvider.commonName().get), inCert.publicKey) + val csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(cnProvider.commonName().get), reqCert.getPublicKey()) csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, new DERPrintableString(cfg.scepChallenge)) + if (cnProvider.alternativeNames().get.nonEmpty) { + val altNames : Array[GeneralName] = cnProvider.alternativeNames().get.map { n=> + log.info(s"Adding alternative dns name [$n] to SCEP certificate request.") + new GeneralName(GeneralName.dNSName, n) + }.toArray + + val names = new GeneralNames(altNames) + val extGen = new ExtensionsGenerator() + val sanExt = extGen.addExtension(Extension.subjectAlternativeName, false, names) + + csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()) + } + // TODO add extensions ? val csrSignerBuilder = new JcaContentSignerBuilder(cfg.csrSignAlgorithm) val csrSigner = csrSignerBuilder.build(privKey) val csr = csrBuilder.build(csrSigner) + dumpCsr(csr + ) + val response = scepClient.enrol(reqCert, privKey, csr) // TODO: Active wait is baaaad @@ -106,7 +150,7 @@ class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider { log.info(s"Retrieved [${certs.length}] certificates from [${cfg.url}].") CertificateHolder.create( - publicKey = inCert.publicKey, + publicKey = reqCert.getPublicKey(), privateKey = Some(privKey), chain = certs ).get diff --git a/blended.security.scep/src/test/scala/blended/security/scep/internal/ScepTestClient.scala b/blended.security.scep/src/test/scala/blended/security/scep/internal/ScepTestClient.scala index 33c3f5d27..a613d7fb2 100644 --- a/blended.security.scep/src/test/scala/blended/security/scep/internal/ScepTestClient.scala +++ b/blended.security.scep/src/test/scala/blended/security/scep/internal/ScepTestClient.scala @@ -1,34 +1,40 @@ package blended.security.scep.internal -import scala.util.Try +import java.io.File + +import blended.security.ssl.internal.{JavaKeystore, MemoryKeystore} -import blended.security.ssl.{ CommonNameProvider, X509CertificateInfo } +import scala.util.Try +import blended.security.ssl.{CommonNameProvider, X509CertificateInfo} import blended.util.logging.Logger object ScepTestClient { private[this] val log = Logger[ScepTestClient] + private val keystore = new JavaKeystore(new File("/tmp/keystore"), "test".toCharArray, Some("test".toCharArray)) + def main(args: Array[String]) : Unit = { log.info("Starting Scep Test Client ...") val cnProvider = new CommonNameProvider { - override def commonName(): Try[String] = Try { "CN=cc9999lnxprx01.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" } - override def alternativeNames(): Try[List[String]] = Try { List("cc9999lnxprx01.9999.cc.kaufland", "cachea.9999.cc.kaufland") } + override def commonName(): Try[String] = Try { "CN=cachea.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" } + //override def commonName(): Try[String] = Try { "CN=cc9999lnxprx01.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" } + override def alternativeNames(): Try[List[String]] = Try { List("cachea.9998.cc.kaufland", "cachea.9999.cc.kaufland") } } val scepConfig = new ScepConfig( - url = "http://iqscep01:8080/pgwy/scep/sib", + url = "http://scep-t.pki.schwarz:8080/pgwy/scep/sib", profile = None, keyLength = 2048, csrSignAlgorithm = "SHA1withRSA", - scepChallenge ="password" + scepChallenge ="qXUDZTttAZDghBguVk2M" ) val provider = new ScepCertificateProvider(scepConfig) - val cert1 = provider.refreshCertificate(None, cnProvider).get + val cert1 = provider.refreshCertificate(None, cnProvider).get.copy(changed = true) cert1.chain.foreach { c => log.info(X509CertificateInfo(c).toString) @@ -36,11 +42,17 @@ object ScepTestClient { log.info("=" * 100) - val cert2 = provider.refreshCertificate(Some(cert1), cnProvider).get + val cert2 = provider.refreshCertificate(Some(cert1), cnProvider).get.copy(changed = true) cert2.chain.foreach { c => log.info(X509CertificateInfo(c).toString) } + + val memStore : MemoryKeystore = new MemoryKeystore(Map.empty) + .update("initial", cert1).get + .update("refreshed", cert2).get + + keystore.saveKeyStore(memStore) } } diff --git a/blended.security.scep/src/test/scala/blended/security/scep/internal/SelfSignedTest.scala b/blended.security.scep/src/test/scala/blended/security/scep/internal/SelfSignedTest.scala new file mode 100644 index 000000000..d207107e0 --- /dev/null +++ b/blended.security.scep/src/test/scala/blended/security/scep/internal/SelfSignedTest.scala @@ -0,0 +1,37 @@ +package blended.security.scep.internal + +import java.io.File + +import blended.security.ssl._ +import blended.security.ssl.internal.{JavaKeystore, MemoryKeystore} + +import scala.util.Try + +object SelfSignedTest { + + private val selfSignedCfg : SelfSignedConfig = SelfSignedConfig( + commonNameProvider = new CommonNameProvider { + override def commonName(): Try[String] = Try { "CN=cachea.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" } + //override def commonName(): Try[String] = Try { "CN=cc9999lnxprx01.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" } + override def alternativeNames(): Try[List[String]] = Try { List("cc9999lnxprx01.9999.cc.kaufland", "cachea.9999.cc.kaufland") } + }, + keyStrength = 2048, + sigAlg = "SHA256withRSA", + validDays = 1 + ) + + private val provider : CertificateProvider = new SelfSignedCertificateProvider(selfSignedCfg) + + def main(args: Array[String]) : Unit = { + + val keystore = new JavaKeystore(new File("/tmp/keystore"), "test".toCharArray, Some("test".toCharArray)) + val memStore = new MemoryKeystore(Map.empty) + + val cert : CertificateHolder = provider.refreshCertificate(None, selfSignedCfg.commonNameProvider).get.copy(changed = true) + println(cert.dump) + + keystore.saveKeyStore(memStore.update("cert", cert).get) + + } + +} diff --git a/blended.security.ssl/src/main/scala/blended/security/ssl/CertificateHolder.scala b/blended.security.ssl/src/main/scala/blended/security/ssl/CertificateHolder.scala index 47edd801d..524e65871 100644 --- a/blended.security.ssl/src/main/scala/blended/security/ssl/CertificateHolder.scala +++ b/blended.security.ssl/src/main/scala/blended/security/ssl/CertificateHolder.scala @@ -39,7 +39,7 @@ case class CertificateHolder ( // A complete dump of the certificate chain as a String def dump : String = { - chain.map { c => c.toString() }.mkString("*" * 30 , "\n\n--- Signed by ---\n\n", "*" * 30) + chain.map { c => c.toString() }.mkString("\n" + "*" * 30 , "\n\n--- Signed by ---\n\n", "*" * 30) } } diff --git a/doc/_notes/ContinousBuild b/doc/_notes/ContinousBuild index 899c618e2..ee104eb47 100644 --- a/doc/_notes/ContinousBuild +++ b/doc/_notes/ContinousBuild @@ -21,13 +21,20 @@ https://jaxenter.de/der-steuermann-fuers-containerschiff-65844 Install Jenkins in Kubernetes https://www.blazemeter.com/blog/how-to-setup-scalable-jenkins-on-top-of-a-kubernetes-cluster +https://www.infoq.com/articles/scaling-docker-with-kubernetes -1. Create jenkins namespace -kubectl create namespace jenkins +Make sure the default account can be used: +https://github.com/fabric8io/fabric8/issues/6840 -Plan: -- create and configure Docker Image for jenkins master - - project blended.helm/docker/jenkins-master -- run image locally on docker only -- deploy image to minikube -- deploy image to kubectl +Sample Jenkins Pipeline +https://github.com/eldada/jenkins-pipeline-kubernetes/blob/master/Jenkinsfile + +Automating jenkins setup +https://dzone.com/articles/dockerizing-jenkins-2-setup-and-using-it-along-wit +https://dzone.com/articles/dockerizing-jenkins-part-2-deployment-with-maven-a +https://dzone.com/articles/securing-password-with-docker-compose-docker-secre +https://dzone.com/articles/putting-jenkins-build-logs-into-elk-stack-filebeat +https://dzone.com/articles/creating-jenkins-configuration-as-code-and-applyin + +Multinode local kubernetes cluster +https://github.com/kinvolk/kube-spawn