Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 39b7ac2e authored by giovanni frison's avatar giovanni frison
Browse files

Merge branch 'release' into 'main'

Release

See merge request !38
parents c0d4b787 cc224964
Branches
Tags v0.5.0
2 merge requests!74Feature/align,!38Release
Pipeline #211700 passed
Showing
with 40 additions and 415 deletions
FROM openjdk:17-jdk-alpine
FROM eclipse-temurin:21-jdk-alpine
RUN adduser -S -u 1001 1001
COPY target/*.jar app.jar
RUN chown 1001 /app.jar
USER 1001
ENTRYPOINT ["java","-jar","/app.jar"]
# Environment Variables Configuration
This document outlines the environment variables required to configure SSL, EJBCA, and certificate management for the application. These variables are essential for securing communications and managing certificates within a Kubernetes environment.
This document outlines the environment variables required to configure the datasource of the application.
You can override this configuration changing the values.yaml
## Overview
The environment variables listed below are used to define the connection details, credentials, and file locations necessary for integrating with EJBCA (Enterprise Java Beans Certificate Authority) and managing SSL certificates. The values are templated using Helm, allowing for customization based on the configuration provided in the Helm chart.
The environment variables listed below are used to define the connection details and credentials for PostgreSQL. The values are templated using Helm, allowing for customization based on the namespace and other values provided in the Helm chart.
## Environment Variables
### Database Configuration
### DataSource Configuration
- **PostgreSQL**
- `SPRING_DATASOURCE_URL`: The JDBC URL for connecting to the PostgreSQL database.
- Format: `jdbc:postgresql://postgresql.<namespace>.svc.cluster.local:5432/onboarding`
- `SPRING_DATASOURCE_USERNAME`: The username for the PostgreSQL database.
- `SPRING_DATASOURCE_PASSWORD`: The password for the PostgreSQL database.
### EJBCA Configuration
- **EJBCA URL and Profile**
- `EJBCA_URL`: The URL for the EJBCA enrollment service.
- Value is derived from `ejbca.enrollConfig.url` specified in the Helm values.
- `EJBCA_PROFILE_NAME`: The profile name to be used when enrolling with EJBCA.
- Value is derived from `ejbca.enrollConfig.profileName` specified in the Helm values.
- `EJBCA_END_ENTITY_NAME`: The name of the end entity in EJBCA.
- Value is derived from `ejbca.enrollConfig.endEntityName` specified in the Helm values.
- `EJBCA_CA_NAME`: The name of the Certificate Authority (CA) in EJBCA.
- Value is derived from `ejbca.enrollConfig.caName` specified in the Helm values.
### SSL Configuration
- **Keystore and Truststore**
- `SPRING_SSL_BUNDLE_JKS_EJBCA_KEYSTORE_PASSWORD`: The password for the EJBCA keystore.
- Value is derived from `ejbca.keystore.password` specified in the Helm values.
- `SPRING_SSL_BUNDLE_JKS_EJBCA_TRUSTSTORE_PASSWORD`: The password for the EJBCA truststore.
- Value is derived from `ejbca.truststore.password` specified in the Helm values.
- `SPRING_SSL_BUNDLE_JKS_EJBCA_KEY_ALIAS`: The alias for the key within the keystore.
- Default value: `superadmin`
- `SPRING_SSL_BUNDLE_JKS_EJBCA_KEYSTORE_LOCATION`: The file path for the EJBCA keystore.
- Default path: `/etc/certs/keystore.p12`
- `SPRING_SSL_BUNDLE_JKS_EJBCA_TRUSTSTORE_LOCATION`: The file path for the EJBCA truststore.
- Default path: `/etc/certs/truststore.jks`
### Certificate Configuration
- `SIMPL_CERTIFICATE_SAN`: The Subject Alternative Name (SAN) for the certificate.
- Value is derived from `global.hostTls` specified in the Helm values.
- `SIMPL_CERTIFICATE_PASSWORD`: The password for the certificate used by the service.
- Value is derived from `global.keystore.password` specified in the Helm values.
- `SPRING_DATASOURCE_URL`: The URL for the datasource connection.
- Value is derived from `db.url` specified in the Helm values.
- `SPRING_DATASOURCE_USERNAME`: The username for the datasource.
- Value is derived from `db.username` specified in the Helm values.
- `SPRING_DATASOURCE_PASSWORD`: The password for the datasource.
- Value is derived from `db.password` specified in the Helm values.
## Usage
......@@ -58,18 +25,9 @@ To use these environment variables, define them in your Kubernetes manifests or
### Example `values.yaml`
```yaml
global:
hostTls: "your-host-tls"
keystore:
password: "your-keystore-password"
db:
url: "jdbc:postgresql://postgresql.{{ .Release.Namespace }}.svc.cluster.local:5432/onboarding"
username: "onboarding"
password: "onboarding"
```
ejbca:
enrollConfig:
url: "https://ejbca.example.com/enroll"
profileName: "your-profile-name"
endEntityName: "your-end-entity-name"
caName: "your-ca-name"
keystore:
password: "your-keystore-password"
truststore:
password: "your-truststore-password"
......@@ -3,17 +3,7 @@ kind: ConfigMap
metadata:
name: {{ .Chart.Name }}-configmap
data:
{{- with .Values.env }}
{{- toYaml . | nindent 2 }}
{{- end }}
SPRING_SSL_BUNDLE_JKS_EJBCA_KEYSTORE_PASSWORD: "{{ .Values.ejbca.keystore.password }}"
EJBCA_URL: "{{ .Values.ejbca.enrollConfig.url }}"
EJBCA_PROFILE_NAME: "{{ .Values.ejbca.enrollConfig.profileName }}"
EJBCA_END_ENTITY_NAME: "{{ .Values.ejbca.enrollConfig.endEntityName }}"
EJBCA_CA_NAME: "{{ .Values.ejbca.enrollConfig.caName }}"
SPRING_SSL_BUNDLE_JKS_EJBCA_TRUSTSTORE_PASSWORD: "{{ .Values.ejbca.truststore.password }}"
SPRING_SSL_BUNDLE_JKS_EJBCA_KEY_ALIAS: "superadmin"
SPRING_SSL_BUNDLE_JKS_EJBCA_KEYSTORE_LOCATION: "/etc/certs/keystore.p12"
SPRING_SSL_BUNDLE_JKS_EJBCA_TRUSTSTORE_LOCATION: "/etc/certs/truststore.jks"
SIMPL_CERTIFICATE_SAN: "{{ .Values.global.hostTls }}"
SIMPL_CERTIFICATE_PASSWORD: "{{ .Values.global.keystore.password }}"
SPRING_DATASOURCE_URL: "{{ .Values.db.url }}"
SPRING_DATASOURCE_USERNAME: "{{ .Values.db.username }}"
SPRING_DATASOURCE_PASSWORD: "{{ .Values.db.password }}"
{{- $ejbcaFilesPath := printf "files/%s/" .Values.ejbca.credentialsFolder -}}
{{- $keystore := printf "%s%s" $ejbcaFilesPath .Values.ejbca.keystore.name -}}
{{- $truststore := printf "%s%s" $ejbcaFilesPath .Values.ejbca.truststore.name -}}
{{- $keystoreContent := .Files.Get $keystore -}}
{{- $truststoreContent := .Files.Get $truststore -}}
{{- if eq $keystoreContent "" -}}
{{- fail (printf "Keystore file not found: %s" $keystore) -}}
{{- end -}}
{{- if eq $truststoreContent "" -}}
{{- fail (printf "Truststore file not found: %s" $truststore) -}}
{{- end -}}
apiVersion: v1
kind: Secret
metadata:
name: ejbca-rest-api-secret
namespace: {{ .Release.Namespace }}
type: Opaque
data:
client-cert: {{ $keystoreContent | b64enc }}
ca-truststore: {{ $truststoreContent | b64enc }}
\ No newline at end of file
......@@ -9,7 +9,7 @@ spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "microservices.fullname" . }}
name: {{ .Chart.Name }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
......
......@@ -24,13 +24,13 @@ podLabels: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
runAsNonRoot: true
runAsUser: 1001
service:
type: ClusterIP
......@@ -87,59 +87,25 @@ autoscaling:
# targetMemoryUtilizationPercentage: 80
# Additional volumes on the output Deployment definition.
volumes:
- name: ejbca-rest-api-client-cert
secret:
secretName: ejbca-rest-api-secret
items:
- key: client-cert
path: keystore.p12
- name: ejbca-rest-api-truststore
secret:
secretName: ejbca-rest-api-secret
items:
- key: ca-truststore
path: truststore.jks
volumes: {}
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts:
- name: ejbca-rest-api-client-cert
mountPath: "/etc/certs/keystore.p12"
subPath: "keystore.p12"
readOnly: true
- name: ejbca-rest-api-truststore
mountPath: "/etc/certs/truststore.jks"
subPath: "truststore.jks"
readOnly: true
volumeMounts: {}
envFrom:
- configMapRef:
name: onboarding-configmap
env:
SPRING_DATASOURCE_URL: "jdbc:postgresql://postgresql.{{ .Release.Namespace }}.svc.cluster.local:5432/onboarding"
SPRING_DATASOURCE_USERNAME: "onboarding"
SPRING_DATASOURCE_PASSWORD: "onboarding"
nodeSelector: {}
tolerations: []
affinity: {}
ejbca:
credentialsFolder: aruba
keystore:
name: keystore.p12
password: "THc77QkT6kf1m2O46DYi9pjZ"
truststore:
name: truststore.jks
password: "ManagementCA"
enrollConfig:
url: "https://ejbca-community-helm.{{ .Release.Namespace }}.svc.cluster.local:30443"
profileName: "Onboarding TLS Profile"
endEntityName: "Onboarding TLS Profile"
caName: "OnBoardingCA"
\ No newline at end of file
db:
url: "jdbc:postgresql://postgresql.{{ .Release.Namespace }}.svc.cluster.local:5432/identityprovider"
username: "identityprovider"
password: "identityprovider"
PROJECT_VERSION_NUMBER="0.0.4"
PROJECT_VERSION_NUMBER="0.5.0"
......@@ -5,7 +5,7 @@
<parent>
<groupId>com.aruba.simpl</groupId>
<artifactId>simpl-parent</artifactId>
<version>0.0.4-SNAPSHOT</version>
<version>0.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
......@@ -56,10 +56,6 @@
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
......@@ -97,7 +93,7 @@
<repositories>
<repository>
<id>gitlab-maven</id>
<id>common-maven</id>
<url>https://code.europa.eu/api/v4/projects/796/packages/maven</url>
</repository>
</repositories>
......
package com.aruba.simpl.onboarding;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
......@@ -10,7 +8,6 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@ConfigurationPropertiesScan
public class OnboardingApplication {
public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());
SpringApplication.run(OnboardingApplication.class, args);
}
}
package com.aruba.simpl.onboarding.configurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "ejbca")
public record EjbcaProperties(String url, String profileName, String endEntityName, String caName) {}
package com.aruba.simpl.onboarding.configurations;
import com.aruba.simpl.onboarding.exchanges.EjbcaExchange;
import org.springframework.boot.autoconfigure.web.client.RestClientSsl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class ExchangeConfig {
@Bean
public EjbcaExchange ejbcaClient(
EjbcaProperties properties, RestClient.Builder restClientBuilder, RestClientSsl ssl) {
var restClient = restClientBuilder
.baseUrl(properties.url())
.apply(ssl.fromBundle("ejbca"))
.build();
var adapter = RestClientAdapter.create(restClient);
var factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(EjbcaExchange.class);
}
}
package com.aruba.simpl.onboarding.configurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "simpl")
public record SimplProperties(CertificateProperties certificate) {
public record CertificateProperties(String san, String password) {}
}
package com.aruba.simpl.onboarding.controllers;
import com.aruba.simpl.common.exceptions.RuntimeWrapperException;
import com.aruba.simpl.common.exchanges.CertificateExchange;
import com.aruba.simpl.common.model.dto.CertificateDTO;
import com.aruba.simpl.common.model.dto.CertificateRequest;
import com.aruba.simpl.onboarding.services.CertificateService;
import io.swagger.v3.oas.annotations.Hidden;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@RestController
public class CertificateController implements CertificateExchange {
private final CertificateService service;
public CertificateController(CertificateService service) {
this.service = service;
}
@Hidden
@Override
public CertificateDTO createCertificate(UUID participantId, CertificateRequest request) {
try {
return service.createCertificate(participantId, request);
} catch (IOException e) {
throw new RuntimeWrapperException(e);
}
}
@Override
public StreamingResponseBody downloadCertificate(@PathVariable UUID certificateId) {
Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getResponse)
.ifPresent(response ->
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=certificate.p12"));
return output -> service.getCertificate(certificateId, output);
}
}
package com.aruba.simpl.onboarding.controllers;
import com.aruba.simpl.common.exchanges.OnboardingCliExchange;
import com.aruba.simpl.onboarding.services.CertificateService;
import java.util.UUID;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CliController implements OnboardingCliExchange {
private final CertificateService certificateService;
public CliController(CertificateService certificateService) {
this.certificateService = certificateService;
}
@Override
public Resource getCertificate(UUID certificateId, HttpHeaders token) {
return certificateService.getCertificate(certificateId);
}
}
package com.aruba.simpl.onboarding.exceptions;
public class CSRException extends RuntimeException {
public CSRException(String message, Throwable cause) {
super(message, cause);
}
}
package com.aruba.simpl.onboarding.exceptions;
import com.aruba.simpl.common.exceptions.StatusException;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class CertificateNotFoundException extends StatusException {
public CertificateNotFoundException(UUID certificateId) {
super("Certificate with id %s not found".formatted(certificateId));
}
}
package com.aruba.simpl.onboarding.exchanges;
import com.aruba.simpl.onboarding.model.requests.PKCS10EnrollRequest;
import com.aruba.simpl.onboarding.model.responses.PKCS10EnrollResponse;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.PostExchange;
public interface EjbcaExchange {
@PostExchange("/ejbca/ejbca-rest-api/v1/certificate/pkcs10enroll")
PKCS10EnrollResponse enroll(@RequestBody PKCS10EnrollRequest request);
}
package com.aruba.simpl.onboarding.model.entities;
import jakarta.persistence.*;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
@Entity
@Table(name = "certificate")
@Getter
@Setter
@Accessors(chain = true)
@ToString
public class Certificate {
@Id
private UUID id;
@Column(name = "content")
@ToString.Exclude
private byte[] content;
}
package com.aruba.simpl.onboarding.model.repositories;
import com.aruba.simpl.onboarding.model.entities.Certificate;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CertificateRepository extends JpaRepository<Certificate, UUID> {}
package com.aruba.simpl.onboarding.model.requests;
import com.aruba.simpl.common.exceptions.RuntimeWrapperException;
import com.aruba.simpl.common.model.dto.CertificateRequest;
import com.aruba.simpl.onboarding.exceptions.CSRException;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
public class EllipticCertificateSignRequest {
private final X500Name subject;
private final KeyPair keyPair;
private final String dnsName;
private final PKCS10CertificationRequest csr;
public EllipticCertificateSignRequest(UUID certificateId, CertificateRequest request, String dnsName) {
this.subject = new X500Name("CN=%s, O=%s".formatted(certificateId, request.getOrganization()));
this.keyPair = generateKeyPair();
this.dnsName = dnsName;
this.csr = generateCSR();
}
public X500Name getSubject() {
return subject;
}
public KeyPair getKeyPair() {
return keyPair;
}
public PKCS10CertificationRequest getCsr() {
return csr;
}
public String getRawCsr() throws IOException {
return "-----BEGIN CERTIFICATE REQUEST-----\n" + Base64.getEncoder().encodeToString(this.csr.getEncoded())
+ "\n-----END CERTIFICATE REQUEST-----\n";
}
private PKCS10CertificationRequest generateCSR() {
try {
var signer = new JcaContentSignerBuilder("SHA256withECDSA").build(keyPair.getPrivate());
var csrBuilder = new PKCS10CertificationRequestBuilder(
subject,
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
if (StringUtils.isNotBlank(this.dnsName)) {
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, buildSAN());
}
return csrBuilder.build(signer);
} catch (Exception e) {
throw new CSRException("Error during CSR initialization", e);
}
}
private KeyPair generateKeyPair() {
try {
var keyPairGenerator = KeyPairGenerator.getInstance("ECDSA");
keyPairGenerator.initialize(256);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeWrapperException(e);
}
}
private Extensions buildSAN() {
var subjectAltName = new GeneralNames(new GeneralName(GeneralName.dNSName, this.dnsName));
var extensionsGenerator = new ExtensionsGenerator();
try {
extensionsGenerator.addExtension(Extension.subjectAlternativeName, false, subjectAltName);
} catch (IOException e) {
throw new CSRException("Error during CSR initialization", e);
}
return extensionsGenerator.generate();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment