Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/superlinter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Exclude bash scripts with Helm templating (they contain {{ }} syntax)
# Exclude qtodo-truststore.java (default package, no package-info.java needed)
FILTER_REGEX_EXCLUDE: .*\.sh\.tpl$|.*qtodo-truststore\.java$
# These are the validation we disable atm
VALIDATE_ANSIBLE: false
VALIDATE_BASH: false
Expand Down
94 changes: 94 additions & 0 deletions charts/qtodo/files/qtodo-truststore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Collection;

/**
* Utility class for importing CA certificates from a PEM bundle into a PKCS12
* truststore.
*/
public final class QtodoTruststore {
/** Conversion factor from milliseconds to seconds. */
private static final double MILLISECONDS_TO_SECONDS = 1000.0;

private QtodoTruststore() {
// Utility class - prevent instantiation
}

/**
* Main entry point for the truststore import utility.
*
* @param args Command line arguments (not used)
* @throws Exception if certificate import fails
*/
public static void main(final String[] args) throws Exception {
long startTime = System.currentTimeMillis();

// Get environment variables
String bundlePath = System.getenv("CA_BUNDLE");
String p12Path = System.getenv("TRUSTSTORE_PATH");
String password = System.getenv("TRUSTSTORE_PASSWORD");

// Validate required environment variables
boolean missingVars = false;
if (bundlePath == null || bundlePath.isEmpty()) {
System.err.println(
"ERROR: CA_BUNDLE environment variable is not set");
missingVars = true;
}
if (p12Path == null || p12Path.isEmpty()) {
System.err.println(
"ERROR: TRUSTSTORE_PATH environment variable is not set");
missingVars = true;
}
if (password == null || password.isEmpty()) {
System.err.println(
"ERROR: TRUSTSTORE_PASSWORD environment variable "
+ "is not set");
missingVars = true;
}

if (missingVars) {
System.exit(1);
}

System.out.println("Converting CA bundle to PKCS12 truststore...");
System.out.println(" CA bundle: " + bundlePath);
System.out.println(" Output: " + p12Path);

// Create empty PKCS12 keystore
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, password.toCharArray());

// Load certificates from PEM bundle
CertificateFactory certFactory =
CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certs;

try (InputStream fis = new FileInputStream(bundlePath)) {
certs = certFactory.generateCertificates(fis);
}

// Import each certificate
int count = 0;
for (Certificate cert : certs) {
String alias = "ztvp-ca-" + String.format("%03d", count);
keyStore.setCertificateEntry(alias, cert);
count++;
}

// Save the keystore
try (FileOutputStream fos = new FileOutputStream(p12Path)) {
keyStore.store(fos, password.toCharArray());
}

long elapsed = System.currentTimeMillis() - startTime;
System.out.println("Successfully imported " + count
+ " certificates into PKCS12 truststore");
System.out.println("Completed in "
+ (elapsed / MILLISECONDS_TO_SECONDS) + " seconds");
}
}
61 changes: 40 additions & 21 deletions charts/qtodo/files/spiffe-vault-client.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def __init__(self):
self.credentials_file = os.getenv(
"CREDENTIALS_FILE", "/etc/credentials.properties"
)
# ZTVP trusted CA bundle (preferred)
self.ztvp_ca_bundle = os.getenv(
"ZTVP_CA_BUNDLE",
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
)
# Service CA (fallback for backward compatibility)
self.service_ca_file = os.getenv(
"SERVICE_CA_FILE",
"/run/secrets/kubernetes.io/serviceaccount/service-ca.crt",
Expand All @@ -54,6 +60,7 @@ def __init__(self):
logger.info(" VAULT_ROLE: %s", self.vault_role)
logger.info(" DB_USERNAME: %s", self.db_username)
logger.info(" CREDENTIALS_FILE: %s", self.credentials_file)
logger.info(" ZTVP_CA_BUNDLE: %s", self.ztvp_ca_bundle)
logger.info(" SERVICE_CA_FILE: %s", self.service_ca_file)
logger.info(" JWT_TOKEN_FILE: %s", self.jwt_token_file)

Expand All @@ -63,10 +70,22 @@ def __init__(self):

# Setup SSL context for CA verification
self.ssl_context = ssl.create_default_context()
if os.path.exists(self.service_ca_file):

# Try ZTVP CA bundle first (contains both ingress and service CAs)
if os.path.exists(self.ztvp_ca_bundle):
self.ssl_context.load_verify_locations(self.ztvp_ca_bundle)
logger.info("Loaded ZTVP trusted CA bundle from: %s", self.ztvp_ca_bundle)
# Fallback to service CA only (for backward compatibility)
elif os.path.exists(self.service_ca_file):
self.ssl_context.load_verify_locations(self.service_ca_file)
logger.info("Loaded service CA from: %s", self.service_ca_file)
else:
logger.warning("Service CA file not found, using default SSL context")
logger.warning(
"No CA certificates found at %s or %s. "
"Using default SSL context. SSL verification may fail.",
self.ztvp_ca_bundle,
self.service_ca_file,
)

def _make_http_request(
self, url, method="GET", data=None, headers=None, timeout=30
Expand Down Expand Up @@ -111,11 +130,11 @@ def _make_http_request(
"text": error_data,
"json": lambda: (json.loads(error_data) if error_data else {}),
}
except URLError as e:
logger.error("URL Error: %s", e)
except URLError:
logger.error("URL Error occurred")
raise
except Exception as e:
logger.error("Request error: %s", e)
except Exception:
logger.error("Request error occurred")
raise

def get_spiffe_token(self):
Expand All @@ -125,8 +144,8 @@ def get_spiffe_token(self):
jwt_svid = source.read()
logger.info("Successfully retrieved SPIFFE JWT token")
return jwt_svid
except Exception as e:
logger.error("Failed to retrieve SPIFFE token: %s", e)
except Exception:
logger.error("Failed to retrieve SPIFFE token")
raise

def authenticate_with_vault(self):
Expand Down Expand Up @@ -167,8 +186,8 @@ def authenticate_with_vault(self):

return True

except Exception as e:
logger.error("Vault authentication error: %s", e)
except Exception:
logger.error("Vault authentication error occurred")
raise

def retrieve_vault_secret(self):
Expand Down Expand Up @@ -204,8 +223,8 @@ def retrieve_vault_secret(self):

return secret_data

except Exception as e:
logger.error("Secret retrieval error: %s", e)
except Exception:
logger.error("Secret retrieval error occurred")
raise

def extract_credentials(self, secret_data):
Expand All @@ -222,8 +241,8 @@ def extract_credentials(self, secret_data):
credentials["db-username"] = self.db_username
return credentials

except Exception as e:
logger.error("Credential extraction error: %s", e)
except Exception:
logger.error("Credential extraction error occurred")
raise

def write_properties_file(self, credentials):
Expand All @@ -243,8 +262,8 @@ def write_properties_file(self, credentials):

logger.info("Credentials written to %s", self.credentials_file)

except Exception as e:
logger.error("Error writing properties file: %s", e)
except Exception:
logger.error("Error writing properties file")
raise

def is_token_renewal_needed(self):
Expand Down Expand Up @@ -291,8 +310,8 @@ def renew_vault_token(self):
)
return False

except Exception as e:
logger.warning("Token renewal error: %s. Re-authenticating...", e)
except Exception:
logger.warning("Token renewal error occurred. Re-authenticating...")
return False

def run(self, init=False):
Expand Down Expand Up @@ -329,8 +348,8 @@ def run(self, init=False):
except KeyboardInterrupt:
logger.info("Received interrupt signal, shutting down...")
break
except Exception as e:
logger.error("Error in main loop: %s", e)
except Exception:
logger.error("Error in main loop")
logger.info("Retrying in 60 seconds...")
time.sleep(60)

Expand All @@ -352,7 +371,7 @@ def main():
manager = VaultCredentialManager()
manager.run(args.init)
except Exception as e:
logger.error("Failed to start credential manager: %s", e)
logger.error("Failed to start credential manager")
raise SystemExit(1) from e


Expand Down
4 changes: 2 additions & 2 deletions charts/qtodo/templates/app-config-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ metadata:
name: qtodo-config-env
namespace: {{ .Release.Namespace }}
data:
{{- if eq .Values.app.oidc.enabled true }}
QUARKUS_OIDC_ENABLED: "{{ .Values.app.oidc.enabled }}"
{{- if eq .Values.app.spire.enabled true }}
QUARKUS_OIDC_ENABLED: "true"
QUARKUS_OIDC_AUTH_SERVER_URL: "{{ default (printf "https://keycloak.%s/realms/ztvp" .Values.global.localClusterDomain) .Values.app.oidc.authServerUrl }}"
QUARKUS_OIDC_CLIENT_ID: "{{ .Values.app.oidc.clientId }}"
QUARKUS_OIDC_APPLICATION_TYPE: "{{ .Values.app.oidc.applicationType }}"
Expand Down
Loading