refactor: remove OIDC env var config and seeder
OIDC configuration is already fully database-backed (oidc_config table, admin API, OidcConfigRepository). Remove the redundant env var binding (SecurityProperties.Oidc), the env-to-DB seeder (oidcConfigSeeder), and the OIDC section from application.yml. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
11
CLAUDE.md
11
CLAUDE.md
@@ -40,18 +40,19 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
|
|||||||
- Maintains agent instance registry with states: LIVE → STALE → DEAD
|
- Maintains agent instance registry with states: LIVE → STALE → DEAD
|
||||||
- Storage: PostgreSQL (TimescaleDB) for structured data, OpenSearch for full-text search
|
- Storage: PostgreSQL (TimescaleDB) for structured data, OpenSearch for full-text search
|
||||||
- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing, bootstrap token for registration
|
- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing, bootstrap token for registration
|
||||||
- OIDC: Optional external identity provider support (token exchange pattern). Configured via `CAMELEER_OIDC_*` env vars
|
- OIDC: Optional external identity provider support (token exchange pattern). Configured via admin API, stored in database (`oidc_config` table)
|
||||||
- User persistence: PostgreSQL `users` table, admin CRUD at `/api/v1/admin/users`
|
- User persistence: PostgreSQL `users` table, admin CRUD at `/api/v1/admin/users`
|
||||||
|
|
||||||
## CI/CD & Deployment
|
## CI/CD & Deployment
|
||||||
|
|
||||||
- CI workflow: `.gitea/workflows/ci.yml` — build → docker → deploy on push to main
|
- CI workflow: `.gitea/workflows/ci.yml` — build → docker → deploy on push to main or feature branches
|
||||||
- Build step skips integration tests (`-DskipITs`) — Testcontainers needs Docker daemon
|
- Build step skips integration tests (`-DskipITs`) — Testcontainers needs Docker daemon
|
||||||
- Docker: multi-stage build (`Dockerfile`), `$BUILDPLATFORM` for native Maven on ARM64 runner, amd64 runtime
|
- Docker: multi-stage build (`Dockerfile`), `$BUILDPLATFORM` for native Maven on ARM64 runner, amd64 runtime
|
||||||
- `REGISTRY_TOKEN` build arg required for `cameleer3-common` dependency resolution
|
- `REGISTRY_TOKEN` build arg required for `cameleer3-common` dependency resolution
|
||||||
- Registry: `gitea.siegeln.net/cameleer/cameleer3-server` (container images)
|
- Registry: `gitea.siegeln.net/cameleer/cameleer3-server` (container images)
|
||||||
- K8s manifests in `deploy/` — PostgreSQL + OpenSearch StatefulSets, server Deployment + NodePort Service (30081)
|
- K8s manifests in `deploy/` — Kustomize base + overlays (main/feature), shared infra (PostgreSQL, OpenSearch, Authentik) as top-level manifests
|
||||||
- Deployment target: k3s at 192.168.50.86, namespace `cameleer`
|
- Deployment target: k3s at 192.168.50.86, namespace `cameleer` (main), `cam-<slug>` (feature branches)
|
||||||
- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `postgres-credentials`, `opensearch-credentials`, `CAMELEER_JWT_SECRET`
|
- Feature branches: isolated namespace, PG schema, OpenSearch index prefix; Traefik Ingress at `<slug>-api.cameleer.siegeln.net`
|
||||||
|
- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `postgres-credentials`, `opensearch-credentials`
|
||||||
- K8s probes: server uses `/api/v1/health`, PostgreSQL uses `pg_isready`, OpenSearch uses `/_cluster/health`
|
- K8s probes: server uses `/api/v1/health`, PostgreSQL uses `pg_isready`, OpenSearch uses `/_cluster/health`
|
||||||
- Docker build uses buildx registry cache + `--provenance=false` for Gitea compatibility
|
- Docker build uses buildx registry cache + `--provenance=false` for Gitea compatibility
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
package com.cameleer3.server.app.security;
|
package com.cameleer3.server.app.security;
|
||||||
|
|
||||||
import com.cameleer3.server.core.security.OidcConfig;
|
|
||||||
import com.cameleer3.server.core.security.OidcConfigRepository;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration class that creates security service beans and validates
|
* Configuration class that creates security service beans and validates
|
||||||
* that required security properties are set.
|
* that required security properties are set.
|
||||||
* <p>
|
* <p>
|
||||||
* Fails fast on startup if {@code CAMELEER_AUTH_TOKEN} is not set.
|
* Fails fast on startup if {@code CAMELEER_AUTH_TOKEN} is not set.
|
||||||
* Seeds OIDC config from env vars into the database if DB is empty.
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties(SecurityProperties.class)
|
@EnableConfigurationProperties(SecurityProperties.class)
|
||||||
public class SecurityBeanConfig {
|
public class SecurityBeanConfig {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SecurityBeanConfig.class);
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public JwtServiceImpl jwtService(SecurityProperties properties) {
|
public JwtServiceImpl jwtService(SecurityProperties properties) {
|
||||||
return new JwtServiceImpl(properties);
|
return new JwtServiceImpl(properties);
|
||||||
@@ -50,36 +41,4 @@ public class SecurityBeanConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Seeds OIDC config from env vars into the database if the DB has no config yet.
|
|
||||||
* This allows initial provisioning via env vars, after which the admin UI takes over.
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public InitializingBean oidcConfigSeeder(SecurityProperties properties,
|
|
||||||
OidcConfigRepository configRepository) {
|
|
||||||
return () -> {
|
|
||||||
if (configRepository.find().isPresent()) {
|
|
||||||
log.debug("OIDC config already present in database, skipping env var seed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SecurityProperties.Oidc envOidc = properties.getOidc();
|
|
||||||
if (envOidc.isEnabled()
|
|
||||||
&& envOidc.getIssuerUri() != null && !envOidc.getIssuerUri().isBlank()
|
|
||||||
&& envOidc.getClientId() != null && !envOidc.getClientId().isBlank()) {
|
|
||||||
OidcConfig config = new OidcConfig(
|
|
||||||
true,
|
|
||||||
envOidc.getIssuerUri(),
|
|
||||||
envOidc.getClientId(),
|
|
||||||
envOidc.getClientSecret() != null ? envOidc.getClientSecret() : "",
|
|
||||||
envOidc.getRolesClaim(),
|
|
||||||
envOidc.getDefaultRoles(),
|
|
||||||
true,
|
|
||||||
"name"
|
|
||||||
);
|
|
||||||
configRepository.save(config);
|
|
||||||
log.info("OIDC config seeded from environment variables: issuer={}", envOidc.getIssuerUri());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.cameleer3.server.app.security;
|
|||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration properties for security settings.
|
* Configuration properties for security settings.
|
||||||
* Bound from the {@code security.*} namespace in application.yml.
|
* Bound from the {@code security.*} namespace in application.yml.
|
||||||
@@ -19,29 +17,6 @@ public class SecurityProperties {
|
|||||||
private String uiPassword;
|
private String uiPassword;
|
||||||
private String uiOrigin;
|
private String uiOrigin;
|
||||||
private String jwtSecret;
|
private String jwtSecret;
|
||||||
private Oidc oidc = new Oidc();
|
|
||||||
|
|
||||||
public static class Oidc {
|
|
||||||
private boolean enabled = false;
|
|
||||||
private String issuerUri;
|
|
||||||
private String clientId;
|
|
||||||
private String clientSecret;
|
|
||||||
private String rolesClaim = "realm_access.roles";
|
|
||||||
private List<String> defaultRoles = List.of("VIEWER");
|
|
||||||
|
|
||||||
public boolean isEnabled() { return enabled; }
|
|
||||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
||||||
public String getIssuerUri() { return issuerUri; }
|
|
||||||
public void setIssuerUri(String issuerUri) { this.issuerUri = issuerUri; }
|
|
||||||
public String getClientId() { return clientId; }
|
|
||||||
public void setClientId(String clientId) { this.clientId = clientId; }
|
|
||||||
public String getClientSecret() { return clientSecret; }
|
|
||||||
public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; }
|
|
||||||
public String getRolesClaim() { return rolesClaim; }
|
|
||||||
public void setRolesClaim(String rolesClaim) { this.rolesClaim = rolesClaim; }
|
|
||||||
public List<String> getDefaultRoles() { return defaultRoles; }
|
|
||||||
public void setDefaultRoles(List<String> defaultRoles) { this.defaultRoles = defaultRoles; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAccessTokenExpiryMs() { return accessTokenExpiryMs; }
|
public long getAccessTokenExpiryMs() { return accessTokenExpiryMs; }
|
||||||
public void setAccessTokenExpiryMs(long accessTokenExpiryMs) { this.accessTokenExpiryMs = accessTokenExpiryMs; }
|
public void setAccessTokenExpiryMs(long accessTokenExpiryMs) { this.accessTokenExpiryMs = accessTokenExpiryMs; }
|
||||||
@@ -59,6 +34,4 @@ public class SecurityProperties {
|
|||||||
public void setUiOrigin(String uiOrigin) { this.uiOrigin = uiOrigin; }
|
public void setUiOrigin(String uiOrigin) { this.uiOrigin = uiOrigin; }
|
||||||
public String getJwtSecret() { return jwtSecret; }
|
public String getJwtSecret() { return jwtSecret; }
|
||||||
public void setJwtSecret(String jwtSecret) { this.jwtSecret = jwtSecret; }
|
public void setJwtSecret(String jwtSecret) { this.jwtSecret = jwtSecret; }
|
||||||
public Oidc getOidc() { return oidc; }
|
|
||||||
public void setOidc(Oidc oidc) { this.oidc = oidc; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,13 +53,7 @@ security:
|
|||||||
ui-password: ${CAMELEER_UI_PASSWORD:admin}
|
ui-password: ${CAMELEER_UI_PASSWORD:admin}
|
||||||
ui-origin: ${CAMELEER_UI_ORIGIN:http://localhost:5173}
|
ui-origin: ${CAMELEER_UI_ORIGIN:http://localhost:5173}
|
||||||
jwt-secret: ${CAMELEER_JWT_SECRET:}
|
jwt-secret: ${CAMELEER_JWT_SECRET:}
|
||||||
oidc:
|
|
||||||
enabled: ${CAMELEER_OIDC_ENABLED:false}
|
|
||||||
issuer-uri: ${CAMELEER_OIDC_ISSUER:}
|
|
||||||
client-id: ${CAMELEER_OIDC_CLIENT_ID:}
|
|
||||||
client-secret: ${CAMELEER_OIDC_CLIENT_SECRET:}
|
|
||||||
roles-claim: ${CAMELEER_OIDC_ROLES_CLAIM:realm_access.roles}
|
|
||||||
default-roles: ${CAMELEER_OIDC_DEFAULT_ROLES:VIEWER}
|
|
||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
api-docs:
|
api-docs:
|
||||||
|
|||||||
Reference in New Issue
Block a user