-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Security via operator, OIDC trusted certificates (#1314)
* Update operator for security configuration * Validate configuration earlier in reconciliation, set conditions * add security testing, minor re-factoring * fix Sonar issues * Configure truststore for OIDC provider * Exclude auth for non-API paths, fix status update, fix dep status chk * map UI variable for PEM truststore * always pull images without SHAs * fix: default replica values in deployment status to zero * Add test for OIDC truststore w/JKS to PEM conversion * Remove dead code, additional testing for invalid CR scenario * Use TLS + truststore with Keycloak for API OIDC tests * Use predefined set of unauthenticated paths instead of non-`/api` paths * Use secure file attributes for temp truststore, add JavaDoc comment * Resolve issues from review feedback and Sonar scanning Signed-off-by: Michael Edgar <[email protected]>
Showing
37 changed files
with
2,772 additions
and
916 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
226 changes: 226 additions & 0 deletions
226
api/src/test/java/com/github/streamshub/console/test/TlsHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
package com.github.streamshub.console.test; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.math.BigInteger; | ||
import java.nio.charset.StandardCharsets; | ||
import java.security.GeneralSecurityException; | ||
import java.security.KeyPair; | ||
import java.security.KeyPairGenerator; | ||
import java.security.KeyStore; | ||
import java.security.SecureRandom; | ||
import java.security.Security; | ||
import java.security.cert.Certificate; | ||
import java.security.cert.CertificateEncodingException; | ||
import java.security.cert.X509Certificate; | ||
import java.time.LocalDateTime; | ||
import java.time.ZoneOffset; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
import java.util.UUID; | ||
|
||
import org.bouncycastle.asn1.x500.X500Name; | ||
import org.bouncycastle.asn1.x509.BasicConstraints; | ||
import org.bouncycastle.asn1.x509.Extension; | ||
import org.bouncycastle.asn1.x509.GeneralName; | ||
import org.bouncycastle.asn1.x509.GeneralNames; | ||
import org.bouncycastle.asn1.x509.KeyUsage; | ||
import org.bouncycastle.cert.X509CertificateHolder; | ||
import org.bouncycastle.cert.X509v3CertificateBuilder; | ||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | ||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; | ||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; | ||
import org.bouncycastle.jce.provider.BouncyCastleProvider; | ||
import org.bouncycastle.operator.ContentSigner; | ||
import org.bouncycastle.operator.OperatorCreationException; | ||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; | ||
import org.bouncycastle.pkcs.PKCS10CertificationRequest; | ||
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; | ||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; | ||
|
||
public class TlsHelper { | ||
|
||
private static final String BC_PROVIDER = "BC"; | ||
private static final String KEY_ALGORITHM = "RSA"; | ||
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; | ||
private static final String PKCS12 = "PKCS12"; | ||
|
||
static { | ||
// Add the BouncyCastle Provider | ||
Security.addProvider(new BouncyCastleProvider()); | ||
} | ||
|
||
public static TlsHelper newInstance() { | ||
try { | ||
return new TlsHelper(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private Certificate rootCA; | ||
private KeyStore keyStore; | ||
private KeyStore trustStore; | ||
private final char[] passphrase = UUID.randomUUID().toString().toCharArray(); | ||
|
||
public Certificate getRootCA() { | ||
return rootCA; | ||
} | ||
|
||
public String getRootCAPem() { | ||
try { | ||
return pemEncodeCertificate(rootCA); | ||
} catch (CertificateEncodingException | IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public KeyStore getKeyStore() { | ||
return keyStore; | ||
} | ||
|
||
public byte[] getKeyStoreBytes() { | ||
return getBytes(keyStore, passphrase); | ||
} | ||
|
||
public KeyStore getTrustStore() { | ||
return trustStore; | ||
} | ||
|
||
public byte[] getTrustStoreBytes() { | ||
return getBytes(trustStore, passphrase); | ||
} | ||
|
||
public char[] getPassphrase() { | ||
return passphrase; | ||
} | ||
|
||
private byte[] getBytes(KeyStore store, char[] passphrase) { | ||
try { | ||
return toByteArray(store, passphrase); | ||
} catch (GeneralSecurityException | IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private TlsHelper() throws Exception { | ||
// Initialize a new KeyPair generator | ||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM, BC_PROVIDER); | ||
keyPairGenerator.initialize(2048); | ||
|
||
LocalDateTime now = LocalDateTime.now(); | ||
Date notBefore = Date.from(now.minusDays(1).toInstant(ZoneOffset.UTC)); | ||
Date notAfter = Date.from(now.plusYears(1).toInstant(ZoneOffset.UTC)); | ||
|
||
KeyPair rootKeyPair = keyPairGenerator.generateKeyPair(); | ||
X509Certificate rootCert = buildCACertificate(rootKeyPair, notBefore, notAfter); | ||
|
||
KeyPair issuedCertKeyPair = keyPairGenerator.generateKeyPair(); | ||
Certificate issuedCert = buildServerCertificate(issuedCertKeyPair, rootKeyPair, rootCert, notBefore, notAfter); | ||
|
||
rootCA = rootCert; | ||
|
||
trustStore = KeyStore.getInstance("JKS"); | ||
trustStore.load(null, passphrase); | ||
trustStore.setCertificateEntry("CACert", rootCert); | ||
|
||
keyStore = KeyStore.getInstance(PKCS12, BC_PROVIDER); | ||
keyStore.load(null, passphrase); | ||
keyStore.setKeyEntry("localhost", issuedCertKeyPair.getPrivate(), null, new Certificate[] { | ||
issuedCert, | ||
rootCert | ||
}); | ||
keyStore.setCertificateEntry("CACert", rootCert); | ||
} | ||
|
||
private X509Certificate buildCACertificate(KeyPair keyPair, Date notBefore, Date notAfter) | ||
throws OperatorCreationException, IOException, GeneralSecurityException { | ||
|
||
BigInteger rootSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); | ||
|
||
// Issued By and Issued To same for root certificate | ||
X500Name rootCertIssuer = new X500Name("CN=root-cert"); | ||
X500Name rootCertSubject = rootCertIssuer; | ||
ContentSigner rootCertContentSigner = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER) | ||
.build(keyPair.getPrivate()); | ||
X509v3CertificateBuilder rootCertBuilder = new JcaX509v3CertificateBuilder(rootCertIssuer, rootSerialNum, | ||
notBefore, notAfter, rootCertSubject, keyPair.getPublic()); | ||
|
||
// Add Extensions | ||
// A BasicConstraint to mark root certificate as CA certificate | ||
JcaX509ExtensionUtils rootCertExtUtils = new JcaX509ExtensionUtils(); | ||
rootCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); | ||
rootCertBuilder.addExtension(Extension.subjectKeyIdentifier, false, | ||
rootCertExtUtils.createSubjectKeyIdentifier(keyPair.getPublic())); | ||
|
||
// Create a cert holder and export to X509Certificate | ||
X509CertificateHolder rootCertHolder = rootCertBuilder.build(rootCertContentSigner); | ||
return new JcaX509CertificateConverter().setProvider(BC_PROVIDER) | ||
.getCertificate(rootCertHolder); | ||
} | ||
|
||
private Certificate buildServerCertificate(KeyPair keyPair, KeyPair signerKeyPair, X509Certificate signerCert, Date notBefore, Date notAfter) | ||
throws GeneralSecurityException, IOException, OperatorCreationException { | ||
|
||
// Generate a new KeyPair and sign it using the Root Cert Private Key | ||
// by generating a CSR (Certificate Signing Request) | ||
X500Name issuedCertSubject = new X500Name("CN=localhost,O=com.github.streamshub"); | ||
BigInteger issuedCertSerialNum = new BigInteger(Long.toString(new SecureRandom().nextLong())); | ||
|
||
PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(issuedCertSubject, | ||
keyPair.getPublic()); | ||
JcaContentSignerBuilder csrBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER); | ||
|
||
// Sign the new KeyPair with the root cert Private Key | ||
ContentSigner csrContentSigner = csrBuilder.build(signerKeyPair.getPrivate()); | ||
PKCS10CertificationRequest csr = p10Builder.build(csrContentSigner); | ||
|
||
// Use the Signed KeyPair and CSR to generate an issued Certificate | ||
// Here serial number is randomly generated. In general, CAs use | ||
// a sequence to generate Serial number and avoid collisions | ||
var issuer = new X500Name(signerCert.getSubjectX500Principal().getName()); | ||
X509v3CertificateBuilder issuedCertBuilder = new X509v3CertificateBuilder(issuer, issuedCertSerialNum, | ||
notBefore, notAfter, csr.getSubject(), csr.getSubjectPublicKeyInfo()); | ||
|
||
JcaX509ExtensionUtils issuedCertExtUtils = new JcaX509ExtensionUtils(); | ||
|
||
// Add Extensions | ||
// Use BasicConstraints to say that this Cert is not a CA | ||
issuedCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); | ||
|
||
// Add Issuer cert identifier as Extension | ||
issuedCertBuilder.addExtension(Extension.authorityKeyIdentifier, false, issuedCertExtUtils.createAuthorityKeyIdentifier(signerCert)); | ||
|
||
// Add intended key usage extension if needed | ||
issuedCertBuilder.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)); | ||
|
||
// Add DNS name is cert is to used for SSL | ||
GeneralNames subjectAltName = new GeneralNames(new GeneralName[] { | ||
new GeneralName(GeneralName.dNSName, "localhost") | ||
}); | ||
issuedCertBuilder.addExtension(Extension.subjectAlternativeName, false, subjectAltName); | ||
|
||
X509CertificateHolder issuedCertHolder = issuedCertBuilder.build(csrContentSigner); | ||
X509Certificate issuedCert = new JcaX509CertificateConverter().setProvider(BC_PROVIDER) | ||
.getCertificate(issuedCertHolder); | ||
|
||
// Verify the issued cert signature against the root (issuer) cert | ||
issuedCert.verify(signerCert.getPublicKey(), BC_PROVIDER); | ||
return issuedCert; | ||
} | ||
|
||
private byte[] toByteArray(KeyStore store, char[] passphrase) throws GeneralSecurityException, IOException { | ||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); | ||
store.store(buffer, passphrase); | ||
return buffer.toByteArray(); | ||
} | ||
|
||
private String pemEncodeCertificate(Certificate certificate) throws IOException, CertificateEncodingException { | ||
ByteArrayOutputStream certificateOut = new ByteArrayOutputStream(); | ||
certificateOut.write("-----BEGIN CERTIFICATE-----\n".getBytes(StandardCharsets.UTF_8)); | ||
certificateOut.write(Base64.getMimeEncoder(80, new byte[] {'\n'}).encode(certificate.getEncoded())); | ||
certificateOut.write("\n-----END CERTIFICATE-----\n".getBytes(StandardCharsets.UTF_8)); | ||
certificateOut.close(); | ||
return new String(certificateOut.toByteArray(), StandardCharsets.UTF_8); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
...tor/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/AuditRule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import java.util.Locale; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
|
||
import io.fabric8.generator.annotation.Required; | ||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class AuditRule extends Rule { | ||
|
||
@Required | ||
Decision decision; | ||
|
||
public Decision getDecision() { | ||
return decision; | ||
} | ||
|
||
public void setDecision(Decision decision) { | ||
this.decision = decision; | ||
} | ||
|
||
public enum Decision { | ||
ALLOWED, | ||
DENIED, | ||
ALL; | ||
|
||
@JsonCreator | ||
public static Decision forValue(String value) { | ||
if ("*".equals(value)) { | ||
return ALL; | ||
} | ||
return valueOf(value.toUpperCase(Locale.ROOT)); | ||
} | ||
} | ||
|
||
} |
17 changes: 17 additions & 0 deletions
17
...rc/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/GlobalSecurity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class GlobalSecurity extends Security { | ||
|
||
private Oidc oidc; | ||
|
||
public Oidc getOidc() { | ||
return oidc; | ||
} | ||
|
||
public void setOidc(Oidc oidc) { | ||
this.oidc = oidc; | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
...src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/KafkaSecurity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class KafkaSecurity extends Security { | ||
} |
66 changes: 66 additions & 0 deletions
66
operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/Oidc.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import com.fasterxml.jackson.annotation.JsonPropertyDescription; | ||
import com.github.streamshub.console.api.v1alpha1.spec.TrustStore; | ||
import com.github.streamshub.console.api.v1alpha1.spec.Value; | ||
|
||
import io.fabric8.generator.annotation.Required; | ||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class Oidc { | ||
|
||
@Required | ||
private String authServerUrl; | ||
private String issuer; | ||
@Required | ||
private String clientId; | ||
@Required | ||
private Value clientSecret; | ||
|
||
@JsonPropertyDescription(""" | ||
Trust store configuration for when the OIDC provider uses \ | ||
TLS certificates signed by an unknown CA. | ||
""") | ||
private TrustStore trustStore; | ||
|
||
public String getAuthServerUrl() { | ||
return authServerUrl; | ||
} | ||
|
||
public void setAuthServerUrl(String authServerUrl) { | ||
this.authServerUrl = authServerUrl; | ||
} | ||
|
||
public String getIssuer() { | ||
return issuer; | ||
} | ||
|
||
public void setIssuer(String issuer) { | ||
this.issuer = issuer; | ||
} | ||
|
||
public String getClientId() { | ||
return clientId; | ||
} | ||
|
||
public void setClientId(String clientId) { | ||
this.clientId = clientId; | ||
} | ||
|
||
public Value getClientSecret() { | ||
return clientSecret; | ||
} | ||
|
||
public void setClientSecret(Value clientSecret) { | ||
this.clientSecret = clientSecret; | ||
} | ||
|
||
public TrustStore getTrustStore() { | ||
return trustStore; | ||
} | ||
|
||
public void setTrustStore(TrustStore trustStore) { | ||
this.trustStore = trustStore; | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/Role.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import java.util.List; | ||
|
||
import io.fabric8.generator.annotation.Required; | ||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class Role { | ||
|
||
@Required | ||
private String name; | ||
|
||
private List<Rule> rules; | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public void setName(String name) { | ||
this.name = name; | ||
} | ||
|
||
public List<Rule> getRules() { | ||
return rules; | ||
} | ||
|
||
public void setRules(List<Rule> rules) { | ||
this.rules = rules; | ||
} | ||
|
||
} |
72 changes: 72 additions & 0 deletions
72
operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/Rule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import java.util.List; | ||
import java.util.Locale; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
|
||
import io.fabric8.generator.annotation.Required; | ||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class Rule { | ||
|
||
/** | ||
* Resources to which this rule applies (required) | ||
*/ | ||
@Required | ||
List<String> resources; | ||
|
||
/** | ||
* Specific resource names to which this rule applies (optional) | ||
*/ | ||
List<String> resourceNames; | ||
|
||
/** | ||
* Privileges/actions that may be performed for subjects having this rule | ||
*/ | ||
@Required | ||
List<Privilege> privileges; | ||
|
||
public List<String> getResources() { | ||
return resources; | ||
} | ||
|
||
public void setResources(List<String> resources) { | ||
this.resources = resources; | ||
} | ||
|
||
public List<String> getResourceNames() { | ||
return resourceNames; | ||
} | ||
|
||
public void setResourceNames(List<String> resourceNames) { | ||
this.resourceNames = resourceNames; | ||
} | ||
|
||
public List<Privilege> getPrivileges() { | ||
return privileges; | ||
} | ||
|
||
public void setPrivileges(List<Privilege> privileges) { | ||
this.privileges = privileges; | ||
} | ||
|
||
public enum Privilege { | ||
CREATE, | ||
DELETE, | ||
GET, | ||
LIST, | ||
UPDATE, | ||
ALL; | ||
|
||
@JsonCreator | ||
public static Privilege forValue(String value) { | ||
if ("*".equals(value)) { | ||
return ALL; | ||
} | ||
return valueOf(value.toUpperCase(Locale.ROOT)); | ||
} | ||
} | ||
|
||
} |
38 changes: 38 additions & 0 deletions
38
...ator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/Security.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import java.util.List; | ||
|
||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public abstract class Security { | ||
|
||
private List<Subject> subjects; | ||
private List<Role> roles; | ||
private List<AuditRule> audit; | ||
|
||
public List<Subject> getSubjects() { | ||
return subjects; | ||
} | ||
|
||
public void setSubjects(List<Subject> subjects) { | ||
this.subjects = subjects; | ||
} | ||
|
||
public List<Role> getRoles() { | ||
return roles; | ||
} | ||
|
||
public void setRoles(List<Role> roles) { | ||
this.roles = roles; | ||
} | ||
|
||
public List<AuditRule> getAudit() { | ||
return audit; | ||
} | ||
|
||
public void setAudit(List<AuditRule> audit) { | ||
this.audit = audit; | ||
} | ||
|
||
} |
40 changes: 40 additions & 0 deletions
40
operator/src/main/java/com/github/streamshub/console/api/v1alpha1/spec/security/Subject.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.github.streamshub.console.api.v1alpha1.spec.security; | ||
|
||
import java.util.List; | ||
|
||
import io.fabric8.generator.annotation.Required; | ||
import io.sundr.builder.annotations.Buildable; | ||
|
||
@Buildable(editableEnabled = false) | ||
public class Subject { | ||
|
||
private String claim; | ||
@Required | ||
private List<String> include; | ||
private List<String> roleNames; | ||
|
||
public String getClaim() { | ||
return claim; | ||
} | ||
|
||
public void setClaim(String claim) { | ||
this.claim = claim; | ||
} | ||
|
||
public List<String> getInclude() { | ||
return include; | ||
} | ||
|
||
public void setInclude(List<String> include) { | ||
this.include = include; | ||
} | ||
|
||
public List<String> getRoleNames() { | ||
return roleNames; | ||
} | ||
|
||
public void setRoleNames(List<String> roleNames) { | ||
this.roleNames = roleNames; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
745 changes: 745 additions & 0 deletions
745
operator/src/main/java/com/github/streamshub/console/dependents/ConfigurationProcessor.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
558 changes: 3 additions & 555 deletions
558
operator/src/main/java/com/github/streamshub/console/dependents/ConsoleSecret.java
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 175 additions & 0 deletions
175
operator/src/main/java/com/github/streamshub/console/dependents/support/ConfigSupport.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package com.github.streamshub.console.dependents.support; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.Base64; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.function.Function; | ||
|
||
import com.github.streamshub.console.ReconciliationException; | ||
import com.github.streamshub.console.api.v1alpha1.Console; | ||
import com.github.streamshub.console.api.v1alpha1.spec.ConfigVars; | ||
import com.github.streamshub.console.api.v1alpha1.spec.Value; | ||
import com.github.streamshub.console.api.v1alpha1.spec.ValueReference; | ||
|
||
import io.fabric8.kubernetes.api.model.ConfigMap; | ||
import io.fabric8.kubernetes.api.model.ConfigMapKeySelector; | ||
import io.fabric8.kubernetes.api.model.HasMetadata; | ||
import io.fabric8.kubernetes.api.model.Secret; | ||
import io.fabric8.kubernetes.api.model.SecretKeySelector; | ||
import io.fabric8.kubernetes.client.KubernetesClientException; | ||
import io.javaoperatorsdk.operator.api.reconciler.Context; | ||
|
||
public class ConfigSupport { | ||
|
||
private ConfigSupport() { | ||
} | ||
|
||
public static String encodeString(String value) { | ||
return encodeBytes(value.getBytes(StandardCharsets.UTF_8)); | ||
} | ||
|
||
public static String encodeBytes(byte[] value) { | ||
return Base64.getEncoder().encodeToString(value); | ||
} | ||
|
||
public static String decodeString(String encodedValue) { | ||
return new String(decodeBytes(encodedValue), StandardCharsets.UTF_8); | ||
} | ||
|
||
public static byte[] decodeBytes(String encodedValue) { | ||
return Base64.getDecoder().decode(encodedValue); | ||
} | ||
|
||
public static void setConfigVars(Console primary, Context<Console> context, Map<String, String> target, ConfigVars source) { | ||
String namespace = primary.getMetadata().getNamespace(); | ||
|
||
source.getValuesFrom().stream().forEach(fromSource -> { | ||
String prefix = fromSource.getPrefix(); | ||
var configMapRef = fromSource.getConfigMapRef(); | ||
var secretRef = fromSource.getSecretRef(); | ||
|
||
if (configMapRef != null) { | ||
copyData(context, target, ConfigMap.class, namespace, configMapRef.getName(), prefix, configMapRef.getOptional(), ConfigMap::getData); | ||
} | ||
|
||
if (secretRef != null) { | ||
copyData(context, target, Secret.class, namespace, secretRef.getName(), prefix, secretRef.getOptional(), Secret::getData); | ||
} | ||
}); | ||
|
||
source.getValues().forEach(configVar -> target.put(configVar.getName(), configVar.getValue())); | ||
} | ||
|
||
@SuppressWarnings("java:S107") // Ignore Sonar warning for too many args | ||
public static <S extends HasMetadata> void copyData(Context<Console> context, | ||
Map<String, String> target, | ||
Class<S> sourceType, | ||
String namespace, | ||
String name, | ||
String prefix, | ||
Boolean optional, | ||
Function<S, Map<String, String>> dataProvider) { | ||
|
||
S source = getResource(context, sourceType, namespace, name, Boolean.TRUE.equals(optional)); | ||
|
||
if (source != null) { | ||
copyData(target, dataProvider.apply(source), prefix, Secret.class.equals(sourceType)); | ||
} | ||
} | ||
|
||
public static void copyData(Map<String, String> target, Map<String, String> source, String prefix, boolean decode) { | ||
source.forEach((key, value) -> { | ||
if (prefix != null) { | ||
key = prefix + key; | ||
} | ||
target.put(key, decode ? decodeString(value) : value); | ||
}); | ||
} | ||
|
||
/** | ||
* Fetch the value from the given valueSpec. The return value | ||
* will be the decoded raw bytes from the data source. | ||
*/ | ||
public static byte[] getValue(Context<Console> context, String namespace, Value valueSpec) { | ||
if (valueSpec == null) { | ||
return null; // NOSONAR : empty array is not wanted when the valueSpec is null | ||
} | ||
|
||
return Optional.ofNullable(valueSpec.getValue()) | ||
.map(ConfigSupport::toBytes) | ||
.or(() -> Optional.ofNullable(valueSpec.getValueFrom()) | ||
.map(ValueReference::getConfigMapKeyRef) | ||
.flatMap(ref -> getValue(context, namespace, ref))) | ||
.or(() -> Optional.ofNullable(valueSpec.getValueFrom()) | ||
.map(ValueReference::getSecretKeyRef) | ||
.flatMap(ref -> getValue(context, namespace, ref))) | ||
.orElse(null); | ||
} | ||
|
||
private static byte[] toBytes(String value) { | ||
return value.getBytes(StandardCharsets.UTF_8); | ||
} | ||
|
||
private static Optional<byte[]> getValue(Context<Console> context, | ||
String namespace, | ||
ConfigMapKeySelector ref) { | ||
|
||
ConfigMap source = getResource(context, ConfigMap.class, namespace, ref.getName(), Boolean.TRUE.equals(ref.getOptional())); | ||
|
||
if (source != null) { | ||
return Optional.ofNullable(source.getData()) | ||
.map(data -> data.get(ref.getKey())) | ||
.map(ConfigSupport::toBytes) | ||
.or(() -> Optional.ofNullable(source.getBinaryData()) | ||
.map(data -> data.get(ref.getKey())) | ||
.map(ConfigSupport::decodeBytes)); | ||
} | ||
|
||
return Optional.empty(); | ||
} | ||
|
||
private static Optional<byte[]> getValue(Context<Console> context, | ||
String namespace, | ||
SecretKeySelector ref) { | ||
|
||
Secret source = getResource(context, Secret.class, namespace, ref.getName(), Boolean.TRUE.equals(ref.getOptional())); | ||
|
||
if (source != null) { | ||
return Optional.ofNullable(source.getData()) | ||
.map(data -> data.get(ref.getKey())) | ||
.map(ConfigSupport::decodeBytes); | ||
} | ||
|
||
return Optional.empty(); | ||
} | ||
|
||
public static <T extends HasMetadata> T getResource( | ||
Context<Console> context, Class<T> resourceType, String namespace, String name) { | ||
return getResource(context, resourceType, namespace, name, false); | ||
} | ||
|
||
public static <T extends HasMetadata> T getResource( | ||
Context<Console> context, Class<T> resourceType, String namespace, String name, boolean optional) { | ||
|
||
T resource; | ||
|
||
try { | ||
resource = context.getClient() | ||
.resources(resourceType) | ||
.inNamespace(namespace) | ||
.withName(name) | ||
.get(); | ||
} catch (KubernetesClientException e) { | ||
throw new ReconciliationException("Failed to retrieve %s resource: %s/%s. Message: %s" | ||
.formatted(resourceType.getSimpleName(), namespace, name, e.getMessage())); | ||
} | ||
|
||
if (resource == null && !optional) { | ||
throw new ReconciliationException("No such %s resource: %s/%s".formatted(resourceType.getSimpleName(), namespace, name)); | ||
} | ||
|
||
return resource; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
352 changes: 352 additions & 0 deletions
352
operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerSecurityTest.java
Large diffs are not rendered by default.
Oops, something went wrong.
393 changes: 103 additions & 290 deletions
393
operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerTest.java
Large diffs are not rendered by default.
Oops, something went wrong.
317 changes: 317 additions & 0 deletions
317
operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerTestBase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
package com.github.streamshub.console; | ||
|
||
import java.time.Duration; | ||
import java.util.Base64; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import java.util.function.Consumer; | ||
|
||
import jakarta.inject.Inject; | ||
|
||
import org.eclipse.microprofile.config.Config; | ||
import org.jboss.logging.Logger; | ||
import org.junit.jupiter.api.BeforeEach; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; | ||
import com.github.streamshub.console.api.v1alpha1.Console; | ||
import com.github.streamshub.console.api.v1alpha1.ConsoleBuilder; | ||
import com.github.streamshub.console.api.v1alpha1.status.Condition; | ||
import com.github.streamshub.console.config.ConsoleConfig; | ||
import com.github.streamshub.console.dependents.ConsoleResource; | ||
import com.github.streamshub.console.dependents.ConsoleSecret; | ||
|
||
import io.fabric8.kubernetes.api.model.ConfigMap; | ||
import io.fabric8.kubernetes.api.model.HasMetadata; | ||
import io.fabric8.kubernetes.api.model.NamespaceBuilder; | ||
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; | ||
import io.fabric8.kubernetes.api.model.Secret; | ||
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder; | ||
import io.fabric8.kubernetes.api.model.apps.Deployment; | ||
import io.fabric8.kubernetes.api.model.networking.v1.Ingress; | ||
import io.fabric8.kubernetes.client.KubernetesClient; | ||
import io.javaoperatorsdk.operator.Operator; | ||
import io.strimzi.api.kafka.Crds; | ||
import io.strimzi.api.kafka.model.kafka.Kafka; | ||
import io.strimzi.api.kafka.model.kafka.KafkaBuilder; | ||
import io.strimzi.api.kafka.model.kafka.listener.KafkaListenerAuthenticationScramSha512; | ||
import io.strimzi.api.kafka.model.kafka.listener.KafkaListenerType; | ||
import io.strimzi.api.kafka.model.user.KafkaUser; | ||
|
||
import static org.awaitility.Awaitility.await; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
import static org.junit.jupiter.api.Assertions.assertNull; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
abstract class ConsoleReconcilerTestBase { | ||
|
||
private static final Logger LOGGER = Logger.getLogger(ConsoleReconcilerTestBase.class); | ||
|
||
protected static final Duration LIMIT = Duration.ofSeconds(10); | ||
protected static final ObjectMapper YAML = new ObjectMapper(new YAMLFactory()); | ||
|
||
protected static final String KAFKA_NS = "ns1"; | ||
protected static final String KAFKA_NAME = "kafka-1"; | ||
|
||
protected static final String CONSOLE_NS = "ns2"; | ||
protected static final String CONSOLE_NAME = "console-1"; | ||
|
||
@Inject | ||
KubernetesClient client; | ||
|
||
@Inject | ||
Config config; | ||
|
||
@Inject | ||
Operator operator; | ||
|
||
Kafka kafkaCR; | ||
|
||
public static <T extends HasMetadata> T apply(KubernetesClient client, T resource) { | ||
client.resource(resource).serverSideApply(); | ||
return client.resource(resource).patchStatus(); | ||
} | ||
|
||
@BeforeEach | ||
void setUp() { | ||
client.resource(Crds.kafka()).serverSideApply(); | ||
client.resource(Crds.kafkaUser()).serverSideApply(); | ||
client.resource(new CustomResourceDefinitionBuilder() | ||
.withNewMetadata() | ||
.withName("routes.route.openshift.io") | ||
.endMetadata() | ||
.withNewSpec() | ||
.withScope("Namespaced") | ||
.withGroup("route.openshift.io") | ||
.addNewVersion() | ||
.withName("v1") | ||
.withNewSubresources() | ||
.withNewStatus() | ||
.endStatus() | ||
.endSubresources() | ||
.withNewSchema() | ||
.withNewOpenAPIV3Schema() | ||
.withType("object") | ||
.withXKubernetesPreserveUnknownFields(true) | ||
.endOpenAPIV3Schema() | ||
.endSchema() | ||
.withStorage(true) | ||
.withServed(true) | ||
.endVersion() | ||
.withNewNames() | ||
.withSingular("route") | ||
.withPlural("routes") | ||
.withKind("Route") | ||
.endNames() | ||
.endSpec() | ||
.build()) | ||
.serverSideApply(); | ||
|
||
var allConsoles = client.resources(Console.class).inAnyNamespace(); | ||
var allKafkas = client.resources(Kafka.class).inAnyNamespace(); | ||
var allKafkaUsers = client.resources(KafkaUser.class).inAnyNamespace(); | ||
var allDeployments = client.resources(Deployment.class).inAnyNamespace().withLabels(ConsoleResource.MANAGEMENT_LABEL); | ||
var allConfigMaps = client.resources(ConfigMap.class).inAnyNamespace().withLabels(ConsoleResource.MANAGEMENT_LABEL); | ||
var allSecrets = client.resources(Secret.class).inAnyNamespace().withLabels(ConsoleResource.MANAGEMENT_LABEL); | ||
var allIngresses = client.resources(Ingress.class).inAnyNamespace().withLabels(ConsoleResource.MANAGEMENT_LABEL); | ||
|
||
allConsoles.delete(); | ||
allKafkas.delete(); | ||
allKafkaUsers.delete(); | ||
allDeployments.delete(); | ||
allConfigMaps.delete(); | ||
allSecrets.delete(); | ||
allIngresses.delete(); | ||
|
||
await().atMost(LIMIT).untilAsserted(() -> { | ||
assertTrue(allConsoles.list().getItems().isEmpty()); | ||
assertTrue(allKafkas.list().getItems().isEmpty()); | ||
assertTrue(allKafkaUsers.list().getItems().isEmpty()); | ||
assertTrue(allDeployments.list().getItems().isEmpty()); | ||
assertTrue(allConfigMaps.list().getItems().isEmpty()); | ||
assertTrue(allSecrets.list().getItems().isEmpty()); | ||
assertTrue(allIngresses.list().getItems().isEmpty()); | ||
}); | ||
|
||
operator.start(); | ||
|
||
client.resource(new NamespaceBuilder() | ||
.withNewMetadata() | ||
.withName(KAFKA_NS) | ||
.withLabels(Map.of("streamshub-operator/test", "true")) | ||
.endMetadata() | ||
.build()) | ||
.serverSideApply(); | ||
|
||
kafkaCR = new KafkaBuilder() | ||
.withNewMetadata() | ||
.withName(KAFKA_NAME) | ||
.withNamespace(KAFKA_NS) | ||
.endMetadata() | ||
.withNewSpec() | ||
.withNewKafka() | ||
.addNewListener() | ||
.withName("listener1") | ||
.withType(KafkaListenerType.INGRESS) | ||
.withPort(9093) | ||
.withTls(true) | ||
.withAuth(new KafkaListenerAuthenticationScramSha512()) | ||
.endListener() | ||
.endKafka() | ||
.endSpec() | ||
.withNewStatus() | ||
.withClusterId(UUID.randomUUID().toString()) | ||
.addNewListener() | ||
.withName("listener1") | ||
.addNewAddress() | ||
.withHost("kafka-bootstrap.example.com") | ||
.withPort(9093) | ||
.endAddress() | ||
.endListener() | ||
.endStatus() | ||
.build(); | ||
|
||
kafkaCR = apply(client, kafkaCR); | ||
|
||
client.resource(new NamespaceBuilder() | ||
.withNewMetadata() | ||
.withName(CONSOLE_NS) | ||
.withLabels(Map.of("streamshub-operator/test", "true")) | ||
.endMetadata() | ||
.build()) | ||
.serverSideApply(); | ||
} | ||
|
||
Console createConsole(ConsoleBuilder builder) { | ||
var meta = new ObjectMetaBuilder(builder.getMetadata()) | ||
.withNamespace(CONSOLE_NS) | ||
.withName(CONSOLE_NAME) | ||
.build(); | ||
|
||
builder = builder.withMetadata(meta); | ||
|
||
return client.resource(builder.build()).create(); | ||
} | ||
|
||
void awaitReady(Console resource) { | ||
await().ignoreException(NullPointerException.class).atMost(LIMIT).untilAsserted(() -> { | ||
var console = client.resources(Console.class) | ||
.inNamespace(resource.getMetadata().getNamespace()) | ||
.withName(resource.getMetadata().getName()) | ||
.get(); | ||
|
||
assertEquals(1, console.getStatus().getConditions().size()); | ||
var condition = console.getStatus().getConditions().iterator().next(); | ||
|
||
assertEquals(Condition.Types.READY, condition.getType(), condition::toString); | ||
assertEquals("True", condition.getStatus(), condition::toString); | ||
assertNull(condition.getReason()); | ||
assertEquals("All resources ready", condition.getMessage(), condition::toString); | ||
}); | ||
} | ||
|
||
void awaitDependentsNotReady(Console resource, String... dependents) { | ||
await().ignoreException(NullPointerException.class).atMost(LIMIT).untilAsserted(() -> { | ||
var console = client.resources(Console.class) | ||
.inNamespace(resource.getMetadata().getNamespace()) | ||
.withName(resource.getMetadata().getName()) | ||
.get(); | ||
|
||
assertEquals(1, console.getStatus().getConditions().size()); | ||
var condition = console.getStatus().getConditions().iterator().next(); | ||
|
||
assertEquals(Condition.Types.READY, condition.getType(), condition::toString); | ||
assertEquals("False", condition.getStatus(), condition::toString); | ||
assertEquals(Condition.Reasons.DEPENDENTS_NOT_READY, condition.getReason(), condition::toString); | ||
|
||
for (String dependent : dependents) { | ||
assertTrue(condition.getMessage().contains(dependent)); | ||
} | ||
}); | ||
} | ||
|
||
void assertInvalidConfiguration(Console resource, Consumer<List<Condition>> assertion) { | ||
await().ignoreException(NullPointerException.class).atMost(LIMIT).untilAsserted(() -> { | ||
var console = client.resources(Console.class) | ||
.inNamespace(resource.getMetadata().getNamespace()) | ||
.withName(resource.getMetadata().getName()) | ||
.get(); | ||
|
||
var conditions = console.getStatus().getConditions(); | ||
assertTrue(conditions.size() > 1); | ||
|
||
var readyCondition = conditions.iterator().next(); | ||
assertEquals(Condition.Types.READY, readyCondition.getType(), readyCondition::toString); | ||
assertEquals("False", readyCondition.getStatus(), readyCondition::toString); | ||
assertEquals(Condition.Reasons.INVALID_CONFIGURATION, readyCondition.getReason(), readyCondition::toString); | ||
|
||
// Ready is always sorted as the first condition for ease of reference | ||
List<Condition> errors = List.copyOf(conditions).subList(1, conditions.size()); | ||
|
||
assertion.accept(errors); | ||
}); | ||
} | ||
|
||
void assertConsoleConfig(Consumer<ConsoleConfig> assertion) { | ||
await().ignoreException(NullPointerException.class).atMost(LIMIT).untilAsserted(() -> { | ||
var consoleSecret = client.secrets() | ||
.inNamespace(CONSOLE_NS) | ||
.withName(CONSOLE_NAME + "-" + ConsoleSecret.NAME) | ||
.get(); | ||
|
||
assertNotNull(consoleSecret); | ||
|
||
String configEncoded = consoleSecret.getData().get("console-config.yaml"); | ||
byte[] configDecoded = Base64.getDecoder().decode(configEncoded); | ||
|
||
LOGGER.debugf("config YAML: %s", new String(configDecoded)); | ||
|
||
ConsoleConfig consoleConfig = YAML.readValue(configDecoded, ConsoleConfig.class); | ||
assertion.accept(consoleConfig); | ||
}); | ||
} | ||
|
||
void setConsoleIngressReady(Console consoleCR) { | ||
var consoleIngress = client.network().v1().ingresses() | ||
.inNamespace(consoleCR.getMetadata().getNamespace()) | ||
.withName("%s-console-ingress".formatted(consoleCR.getMetadata().getName())) | ||
.get(); | ||
|
||
consoleIngress = consoleIngress.edit() | ||
.editOrNewStatus() | ||
.withNewLoadBalancer() | ||
.addNewIngress() | ||
.withHostname("ingress.example.com") | ||
.endIngress() | ||
.endLoadBalancer() | ||
.endStatus() | ||
.build(); | ||
client.resource(consoleIngress).patchStatus(); | ||
LOGGER.info("Set ingress status for Console ingress"); | ||
} | ||
|
||
Deployment setDeploymentReady(Console consoleCR, String deploymentName) { | ||
var deployment = client.apps().deployments() | ||
.inNamespace(consoleCR.getMetadata().getNamespace()) | ||
.withName("%s-%s".formatted(consoleCR.getMetadata().getName(), deploymentName)) | ||
.editStatus(this::setReady); | ||
LOGGER.infof("Set ready replicas for deployment: %s", deploymentName); | ||
return deployment; | ||
} | ||
|
||
Deployment setReady(Deployment deployment) { | ||
int desiredReplicas = Optional.ofNullable(deployment.getSpec().getReplicas()).orElse(1); | ||
|
||
return deployment.edit() | ||
.editOrNewStatus() | ||
.withReplicas(desiredReplicas) | ||
.withUpdatedReplicas(desiredReplicas) | ||
.withAvailableReplicas(desiredReplicas) | ||
.withReadyReplicas(desiredReplicas) | ||
.endStatus() | ||
.build(); | ||
} | ||
} |
Binary file added
BIN
+5.37 KB
operator/src/test/resources/com/github/streamshub/console/kube-certs.jks
Binary file not shown.
121 changes: 121 additions & 0 deletions
121
operator/src/test/resources/com/github/streamshub/console/kube-certs.pem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIDMjCCAhqgAwIBAgIIMYTYDdowPTswDQYJKoZIhvcNAQELBQAwNzESMBAGA1UE | ||
CxMJb3BlbnNoaWZ0MSEwHwYDVQQDExhrdWJlLWFwaXNlcnZlci1sYi1zaWduZXIw | ||
HhcNMjQxMDIxMDc1NjU5WhcNMzQxMDE5MDc1NjU5WjA3MRIwEAYDVQQLEwlvcGVu | ||
c2hpZnQxITAfBgNVBAMTGGt1YmUtYXBpc2VydmVyLWxiLXNpZ25lcjCCASIwDQYJ | ||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALnWRIe7l/94zj624Ax8lGDdfoppPO9i | ||
EtnkBBjAhkfe3ChnL33b+edeGf9lfIxZYbMVng+tEZhq2RHrp40ZSA1BZ74TwTaQ | ||
1FfaSLU1dMNIWvgudNQMcgDNXTxXRamup5/wZ5udKYUBLVPvFEvmJ+je9QCwEGKQ | ||
JrpDX+aKJOLPKyxVox6ZcqBTKJts+/f6fEqrbDwdlQhGAZRSsfXYZgufSSvRO7gN | ||
67tp3KJ9OhuEzvkMoSFvvQxPmxVlWGKdkZFdKNU1vSI0aOBLlCWHOBmk+Wvu/fTi | ||
iuBybWooaQIvu3CSEXGSJaQ+b4Ol/Uj0EO70Q58HzFndst6wxwD+ZGECAwEAAaNC | ||
MEAwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFAj8 | ||
c+01Kl+BDcIolkavVzAU1G41MA0GCSqGSIb3DQEBCwUAA4IBAQBL97dVCrkgo6ca | ||
7/4nZoP1i7owpWV0hfJWU/TKSFBa7Vzbe7xWyT/HBzjPikoUwZEpH7rZAcHwcYQr | ||
tTozW/zDOZS98cnrepY/tAXVi7Hz5wnuaguI3iwFaIVh9OR8FBZ5TAMaXGW1mYEg | ||
q0jNZY5cbFm+3bacRKSF//hS/3nms3o3b6uni2f4rZGED4iW+zK2qXZfz+B/uCwA | ||
1KoHt3TxZsJ8scVXCMJQi7T0cdjR9pGucCRVFoXKxGE0sIL28ajBdOAIalTh/Wh4 | ||
F6n73xM2Ao84Qi1qk6HrWJHQb1DY/L/+tZc5TlEHbAZwHR3Y8nYpxTlvNYTcuft+ | ||
dGv8WhYX | ||
-----END CERTIFICATE----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIDQDCCAiigAwIBAgIINykiDqNyBqwwDQYJKoZIhvcNAQELBQAwPjESMBAGA1UE | ||
CxMJb3BlbnNoaWZ0MSgwJgYDVQQDEx9rdWJlLWFwaXNlcnZlci1sb2NhbGhvc3Qt | ||
c2lnbmVyMB4XDTI0MTAyMTA3NTY1OFoXDTM0MTAxOTA3NTY1OFowPjESMBAGA1UE | ||
CxMJb3BlbnNoaWZ0MSgwJgYDVQQDEx9rdWJlLWFwaXNlcnZlci1sb2NhbGhvc3Qt | ||
c2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw7zTqYxszydv | ||
LjATLq9ca+rf9URZfiBhKSVWFbM7tRH/gUVsdTPsmq35FfUKFCn6T9UHT+rgOGKA | ||
Z+PqMoDAMOe2QAYPdPUGPHgy5Op2iLfFPagUuOA28avqdryRUXfjMqZpx0EEg6kp | ||
X9O5nOfVKBNYdSlWB5ZGvWl4rUuuUyU+OrzDnyvozRrEvbUt0bLMX4JdYT3u7mlB | ||
MAP+UXKg3qxes3huHP7PSbXRGCV2o5zZzmy0WSxPx2xg0BN3DQnIqxYx8o2w0A8O | ||
Cy1tZAduQZWP33uluFMubRCws3pjRsIUJozhM+POjbEI3e2nmwPRmIuwkJ+u3gYV | ||
58tlkahuBwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/BAUwAwEB | ||
/zAdBgNVHQ4EFgQUSCtp8dg6T1kO1V7KFkeP6Ioo7RYwDQYJKoZIhvcNAQELBQAD | ||
ggEBACTBDHiGLxRzkT++oWh8n9aYVo9kmq3pCy5xyh0vDGY5YublnrLIX0bf8aL9 | ||
rpGrs9WKqi2z/1HtUm6XNJP9eO+Vt08FUP63RSkPQpU+w4qwbsKqu6frbyyIddpK | ||
caUDWA1ggor3aV7umz2F4n6wVg2HDKDBrR/JHeEpWFOeXX2KeDFkomTot93RtCkd | ||
yo6Y/6PYVFfj+SW9rI+b1WQ9U0BqRjgFzRPzp9wryx6c8n05mTcH3C6vxwrifr8v | ||
NLaxI+xVaMtLALJ6pK1lVAPOkS0J5JaewZqOCXySTf2lKn6KC0xJYC0vHdKf27oi | ||
UU33fkByf2O1XayqoEfCXjZ6uPA= | ||
-----END CERTIFICATE----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIDTDCCAjSgAwIBAgIIJM+hLK9EGE0wDQYJKoZIhvcNAQELBQAwRDESMBAGA1UE | ||
CxMJb3BlbnNoaWZ0MS4wLAYDVQQDEyVrdWJlLWFwaXNlcnZlci1zZXJ2aWNlLW5l | ||
dHdvcmstc2lnbmVyMB4XDTI0MTAyMTA3NTY1OVoXDTM0MTAxOTA3NTY1OVowRDES | ||
MBAGA1UECxMJb3BlbnNoaWZ0MS4wLAYDVQQDEyVrdWJlLWFwaXNlcnZlci1zZXJ2 | ||
aWNlLW5ldHdvcmstc2lnbmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC | ||
AQEAoSQzZgP2w02xuh7zlmzAN4hYg2Saz9NMescrUlfCCtqNIVJPCCTx45m2tbhF | ||
jUNv84zjtrJ81BOugyEGQchdjJGoKUdGNcpVUC1Ts/jrRnyuVrmvifjgRl1lFi6u | ||
l5G0jGsgv7Z2W1JT5EpIfaA0qlsUnecdQtm7qienUeWRO9HfYJ08eNaUF+zAB6JA | ||
i8I6AzTl6rJT33EmPymNpXrHFVgr/IDHs2jFakjTauPIGScRxFRze0JTMSvlz/8j | ||
YIu6g59THds1ROm2+NYblcES3zuZeHQ9n4iRalIH8pQ8LQ9lQJ79S4dkYYo++Klu | ||
W60erjZt20zdMgIKnwTtnAY0mQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAqQwDwYD | ||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUMWWSZ6Bg1Rs/dGd+Yvl4m05aDowwDQYJ | ||
KoZIhvcNAQELBQADggEBAEc3IMR1duWEuwIqNQC42TX5upKEAM/d065A4kjiLn3k | ||
ACyFoB7gqA55fy23kTDsAPqcRPeSvwJstUOxIq0eP1q+HUFbdPXa2ORiTmPihY/u | ||
oagSkEMQvSi6vTFKl2wTrdpHZzfk1FgYn6kLwX8LYXyHziS6uRxQHPIvUmgCIs6I | ||
MeHWgJh2rKhX37YD6aW7uv2waif5qs6/pUhDqVoafqmvXp4FMNBLbA6JDE8PcVHA | ||
rFHOvhQJ0FtT4VPwll9/VR9aE7pWBZA6fwvbThWB8WILFGsXbF3bcuR0fu1WZte0 | ||
RtkV+Ps4/zb9TZAkdy1TcIRxvSxVrarHUJRNdtCZfUk= | ||
-----END CERTIFICATE----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIDlzCCAn+gAwIBAgIIK6oSarI03tkwDQYJKoZIhvcNAQELBQAwWTFXMFUGA1UE | ||
AwxOb3BlbnNoaWZ0LWt1YmUtYXBpc2VydmVyLW9wZXJhdG9yX2xvY2FsaG9zdC1y | ||
ZWNvdmVyeS1zZXJ2aW5nLXNpZ25lckAxNzI5NDk4Njk0MB4XDTI0MTAyMTA4MTgx | ||
NFoXDTM0MTAxOTA4MTgxNVowWTFXMFUGA1UEAwxOb3BlbnNoaWZ0LWt1YmUtYXBp | ||
c2VydmVyLW9wZXJhdG9yX2xvY2FsaG9zdC1yZWNvdmVyeS1zZXJ2aW5nLXNpZ25l | ||
ckAxNzI5NDk4Njk0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZPM | ||
58DFXDJfatBHxoxJY2gpSI3rSxF7RHxmIghQxVQQHMD2ny4gkuflyrUYO2VnVZ+v | ||
RfLxl0lhTdx0hLiKwniWqt6rmj+7/0oVbEW2cbWd+OVDos841LxhLZOwrHh+WcQW | ||
spH3NgczP846uPg/yAKEUWX0xAej0lfD1//qr+VdUbhtx7xDl42Jzzt/Me9WS1Lh | ||
J+tHU8Ooa+U2yTX/mGjQgwxfB4qoczoXvpvv1hO35g/wHiKeuOvUCmgzYrPQPCFK | ||
3lx3ETKzC1m+MiRqYYqMDW4DCD43khSTF7XAbFjSMKqk6KrOi4xqhmjyTRZDyX7m | ||
wRun84930lP3/U1ZgQIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAqQwDwYDVR0TAQH/ | ||
BAUwAwEB/zAdBgNVHQ4EFgQU6ZTgKTS9WAZ8+8OZZvy5Xu9wMnwwHwYDVR0jBBgw | ||
FoAU6ZTgKTS9WAZ8+8OZZvy5Xu9wMnwwDQYJKoZIhvcNAQELBQADggEBAFO6LKg7 | ||
ILwywt/52kbfPRrEvpb5p3T4ANs1c50sU0YewbvT1phhbX0xG63kNm6isuZSLCie | ||
7aNLDuEAjv4HmY4QffGvKHgyIQsII8+/W7JmS+nRgPEI6Yj3tJmy3gvN3X0xrBdt | ||
S96+jCag1aR58zJ9imRaZOBNNlE4aedbvllFZ2k4Gk4BSZjqSJhNZSPaZmWUNsAH | ||
nq/t16ZKs43aLtwqBRTI3ssGmcZjMTNeFVXVV0/WmjIRAnBvJHipzwZvZEyqw+EL | ||
aJIz1fio6X1uGPyIBr4sp/p7Do0eUc+euEi1kLmctsDmntfJzD/WJLDh1qf7t/Ko | ||
5NspdFu8g+UVpsg= | ||
-----END CERTIFICATE----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIDWzCCAkOgAwIBAgIIIO4v2+5yCt0wDQYJKoZIhvcNAQELBQAwJjEkMCIGA1UE | ||
AwwbaW5ncmVzcy1vcGVyYXRvckAxNzI5NDk4Mjk0MB4XDTI0MTAyMTA4MTEzNVoX | ||
DTI2MTAyMTA4MTEzNlowHTEbMBkGA1UEAwwSKi5hcHBzLWNyYy50ZXN0aW5nMIIB | ||
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA46vu506RtAKNTOemgaD1kryD | ||
x0QzhjqyaS6XLUhiEpimfaSl1erPJJyjpvPylZpXMPqodQbTVaCOhbmNPFOFygEL | ||
sfB1mpIGKNokTtm2mHE+YhfMLhAOb0OuSAEybk3EqZGxLrUQwVp88owYJHe3bPAG | ||
wG6mMR6Sn/eL3shxpWfgpmVYnATyt2/7qpdTequxyOhasr2KIxD4ScvCwzycgTGS | ||
xqEV2rSczmyZRWNdyw8p+V8394Uow5r7W9s2mUYC/KF6fvAs2RavKtSFD7ZLoBBW | ||
xJmkzrT1uq5duOGgadnKZBo3IMj45zG8PooZ3cDepAVk3+vG3MfTpWHvZFOdywID | ||
AQABo4GVMIGSMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAM | ||
BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBS82jT8YPTXx+3VZ4i5DEfntaJzxzAfBgNV | ||
HSMEGDAWgBTp+7an7Jz7tRjmpnBveqo1hpNIsTAdBgNVHREEFjAUghIqLmFwcHMt | ||
Y3JjLnRlc3RpbmcwDQYJKoZIhvcNAQELBQADggEBAFZ7O3aoPG+OMnVuLlg6S6/I | ||
d1UeyBLdftVWzeB2Y0yzCb6PCPyuj1CVT7k64mqFLCYp/HT7E5QgmsFUDz62Q0Os | ||
bLhKoRcBnPZnk2m58QQf8h4rzvc1oEgrhhYP3KW+RLfmwfAIVLXDzhXhEOejaC+B | ||
mkvYhRsYCW7X9hc/+UrhCWpLFXASXTtJIitbNRHVFfgRMm71mh9NhwFRAQBexwO6 | ||
0ZG0MKapIaYWVknGrNPDTauoXaxyX6WQxM3VqLujCzHKz2OQmsylIcCXG/xtUfhU | ||
JjBImOIsbWvvE3KWfPCA+rXg1MzoUagmECfjkq9uABqooIUxxksCRTb1wqj3DW0= | ||
-----END CERTIFICATE----- | ||
-----BEGIN CERTIFICATE----- | ||
MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtpbmdy | ||
ZXNzLW9wZXJhdG9yQDE3Mjk0OTgyOTQwHhcNMjQxMDIxMDgxMTMzWhcNMjYxMDIx | ||
MDgxMTM0WjAmMSQwIgYDVQQDDBtpbmdyZXNzLW9wZXJhdG9yQDE3Mjk0OTgyOTQw | ||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpCyfKVGuk6af3hAdOqaPi | ||
cEpF3NHMhZDfBHcnCSVz5U7NiBVkDBuYoQbHrFFNnnAO1TWH/ztwLtjH8odUelWa | ||
84Ue7xpDOrCDgNBIEsB7ymoV4oyRw3DVuuC9kfAWx6+YhuP6hEltOVwJvXdC50A7 | ||
SKQTuDUSjF7VZF1RXQW8CBJO+2/cwuXhC+O9z3VHuSTzAUFDsycTnVwC8uB7Ycn/ | ||
7t/UVspP9Es0YMHlmdw6eobGm3xm14UqCKYkySygtXWPTfqPXonfDIMQeu0E0eil | ||
Cg/TSSvm4CJB1u1JrpehzDsVUEOZPnBuO8axY2Rv5MBE3mMJVWzUN6oj5OOp06Gz | ||
AgMBAAGjRTBDMA4GA1UdDwEB/wQEAwICpDASBgNVHRMBAf8ECDAGAQH/AgEAMB0G | ||
A1UdDgQWBBTp+7an7Jz7tRjmpnBveqo1hpNIsTANBgkqhkiG9w0BAQsFAAOCAQEA | ||
DIfuWUfB+lgrOE6qTpF5R+lbBu9oQr7XXLYFnOBjSdTr/V7tJr6GmBO5G9vVm57N | ||
bAGQekVLDMtjvbbHtM3wmOW7O5g0wykMl/uHiHKbtfYEZ89CLxxdYOjQpzgzJHhF | ||
QSfpvdFFG55+/9Gdb1yUJHZ5P54UgVGNtiX3Hnch/FwU5avPD6PAr5a4OrSp++/S | ||
zB4Aw1vhO+4uage+j/TW6uF3YJQT/thVWG8z2vXJuej+i/HBjiviHMEQVdC13LaI | ||
uy7oM4B/BrDebtUD+blCgOYZs24sWu2eiCtqKW5dtxveNcN/Hq1py4xPOqkESfic | ||
29vnSmtl5vitgVMcMNKneQ== | ||
-----END CERTIFICATE----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters