Compare commits
1 Commits
feature/ad
...
feature/pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba7cd96107 |
@@ -1,7 +0,0 @@
|
|||||||
# Orchard Local Development Environment
|
|
||||||
# Copy this file to .env and customize as needed
|
|
||||||
# Note: .env is gitignored and will not be committed
|
|
||||||
|
|
||||||
# Admin account password (required for local development)
|
|
||||||
# This sets the initial admin password when the database is first created
|
|
||||||
ORCHARD_ADMIN_PASSWORD=changeme123
|
|
||||||
@@ -15,7 +15,6 @@ variables:
|
|||||||
STAGE_RDS_HOST: orchard-stage.cluster-cvw3jzjkozoc.us-gov-west-1.rds.amazonaws.com
|
STAGE_RDS_HOST: orchard-stage.cluster-cvw3jzjkozoc.us-gov-west-1.rds.amazonaws.com
|
||||||
STAGE_RDS_DBNAME: postgres
|
STAGE_RDS_DBNAME: postgres
|
||||||
STAGE_SECRET_ARN: "arn:aws-us-gov:secretsmanager:us-gov-west-1:052673043337:secret:rds!cluster-a573672b-1a38-4665-a654-1b7df37b5297-IaeFQL"
|
STAGE_SECRET_ARN: "arn:aws-us-gov:secretsmanager:us-gov-west-1:052673043337:secret:rds!cluster-a573672b-1a38-4665-a654-1b7df37b5297-IaeFQL"
|
||||||
STAGE_AUTH_SECRET_ARN: "arn:aws-us-gov:secretsmanager:us-gov-west-1:052673043337:secret:orchard-stage-creds-SMqvQx"
|
|
||||||
STAGE_S3_BUCKET: orchard-artifacts-stage
|
STAGE_S3_BUCKET: orchard-artifacts-stage
|
||||||
AWS_REGION: us-gov-west-1
|
AWS_REGION: us-gov-west-1
|
||||||
# Shared pip cache directory
|
# Shared pip cache directory
|
||||||
@@ -118,9 +117,6 @@ release:
|
|||||||
- pip install --index-url "$PIP_INDEX_URL" pytest pytest-asyncio httpx
|
- pip install --index-url "$PIP_INDEX_URL" pytest pytest-asyncio httpx
|
||||||
script:
|
script:
|
||||||
- cd backend
|
- cd backend
|
||||||
# Debug: Print environment variables for test configuration
|
|
||||||
- echo "ORCHARD_TEST_URL=$ORCHARD_TEST_URL"
|
|
||||||
- echo "ORCHARD_TEST_PASSWORD is set to '${ORCHARD_TEST_PASSWORD:-NOT SET}'"
|
|
||||||
# Run full integration test suite, excluding:
|
# Run full integration test suite, excluding:
|
||||||
# - large/slow tests
|
# - large/slow tests
|
||||||
# - requires_direct_s3 tests (can't access MinIO from outside K8s cluster)
|
# - requires_direct_s3 tests (can't access MinIO from outside K8s cluster)
|
||||||
@@ -200,13 +196,14 @@ release:
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
PYTEST_SCRIPT
|
PYTEST_SCRIPT
|
||||||
|
|
||||||
# Reset stage template - runs from CI runner, uses CI variable for auth
|
# Integration tests for stage deployment (full suite)
|
||||||
|
# Reset stage template - shared by pre and post test reset jobs
|
||||||
# Calls the /api/v1/admin/factory-reset endpoint which handles DB and S3 cleanup
|
# Calls the /api/v1/admin/factory-reset endpoint which handles DB and S3 cleanup
|
||||||
.reset_stage_template: &reset_stage_template
|
.reset_stage_template: &reset_stage_template
|
||||||
stage: deploy
|
stage: deploy
|
||||||
image: deps.global.bsf.tools/docker/python:3.12-slim
|
image: deps.global.bsf.tools/docker/python:3.12-slim
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
retry: 1
|
retry: 1 # Retry once on transient failures
|
||||||
before_script:
|
before_script:
|
||||||
- pip install --index-url "$PIP_INDEX_URL" httpx
|
- pip install --index-url "$PIP_INDEX_URL" httpx
|
||||||
script:
|
script:
|
||||||
@@ -219,22 +216,19 @@ release:
|
|||||||
|
|
||||||
BASE_URL = os.environ.get("STAGE_URL", "")
|
BASE_URL = os.environ.get("STAGE_URL", "")
|
||||||
ADMIN_USER = "admin"
|
ADMIN_USER = "admin"
|
||||||
ADMIN_PASS = os.environ.get("STAGE_ADMIN_PASSWORD", "")
|
ADMIN_PASS = "changeme123" # Default admin password
|
||||||
MAX_RETRIES = 3
|
MAX_RETRIES = 3
|
||||||
RETRY_DELAY = 5
|
RETRY_DELAY = 5 # seconds
|
||||||
|
|
||||||
if not BASE_URL:
|
if not BASE_URL:
|
||||||
print("ERROR: STAGE_URL not set")
|
print("ERROR: STAGE_URL environment variable not set")
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not ADMIN_PASS:
|
|
||||||
print("ERROR: STAGE_ADMIN_PASSWORD not set")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print(f"=== Resetting stage environment at {BASE_URL} ===")
|
print(f"=== Resetting stage environment at {BASE_URL} ===")
|
||||||
|
|
||||||
def do_reset():
|
def do_reset():
|
||||||
with httpx.Client(base_url=BASE_URL, timeout=120.0) as client:
|
with httpx.Client(base_url=BASE_URL, timeout=120.0) as client:
|
||||||
|
# Login as admin
|
||||||
print("Logging in as admin...")
|
print("Logging in as admin...")
|
||||||
login_response = client.post(
|
login_response = client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
@@ -244,6 +238,7 @@ release:
|
|||||||
raise Exception(f"Login failed: {login_response.status_code} - {login_response.text}")
|
raise Exception(f"Login failed: {login_response.status_code} - {login_response.text}")
|
||||||
print("Login successful")
|
print("Login successful")
|
||||||
|
|
||||||
|
# Call factory reset endpoint
|
||||||
print("Calling factory reset endpoint...")
|
print("Calling factory reset endpoint...")
|
||||||
reset_response = client.post(
|
reset_response = client.post(
|
||||||
"/api/v1/admin/factory-reset",
|
"/api/v1/admin/factory-reset",
|
||||||
@@ -261,6 +256,7 @@ release:
|
|||||||
else:
|
else:
|
||||||
raise Exception(f"Factory reset failed: {reset_response.status_code} - {reset_response.text}")
|
raise Exception(f"Factory reset failed: {reset_response.status_code} - {reset_response.text}")
|
||||||
|
|
||||||
|
# Retry loop
|
||||||
for attempt in range(1, MAX_RETRIES + 1):
|
for attempt in range(1, MAX_RETRIES + 1):
|
||||||
try:
|
try:
|
||||||
print(f"Attempt {attempt}/{MAX_RETRIES}")
|
print(f"Attempt {attempt}/{MAX_RETRIES}")
|
||||||
@@ -284,14 +280,12 @@ reset_stage_pre:
|
|||||||
<<: *reset_stage_template
|
<<: *reset_stage_template
|
||||||
needs: [deploy_stage]
|
needs: [deploy_stage]
|
||||||
|
|
||||||
# Integration tests for stage deployment
|
# Integration tests for stage deployment (full suite)
|
||||||
# Uses CI variable STAGE_ADMIN_PASSWORD (set in GitLab CI/CD settings)
|
|
||||||
integration_test_stage:
|
integration_test_stage:
|
||||||
<<: *integration_test_template
|
<<: *integration_test_template
|
||||||
needs: [reset_stage_pre]
|
needs: [reset_stage_pre]
|
||||||
variables:
|
variables:
|
||||||
ORCHARD_TEST_URL: $STAGE_URL
|
ORCHARD_TEST_URL: $STAGE_URL
|
||||||
ORCHARD_TEST_PASSWORD: $STAGE_ADMIN_PASSWORD
|
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||||
when: on_success
|
when: on_success
|
||||||
@@ -303,13 +297,11 @@ reset_stage:
|
|||||||
allow_failure: true # Don't fail pipeline if reset has issues
|
allow_failure: true # Don't fail pipeline if reset has issues
|
||||||
|
|
||||||
# Integration tests for feature deployment (full suite)
|
# Integration tests for feature deployment (full suite)
|
||||||
# Uses DEV_ADMIN_PASSWORD CI variable (same as deploy_feature)
|
|
||||||
integration_test_feature:
|
integration_test_feature:
|
||||||
<<: *integration_test_template
|
<<: *integration_test_template
|
||||||
needs: [deploy_feature]
|
needs: [deploy_feature]
|
||||||
variables:
|
variables:
|
||||||
ORCHARD_TEST_URL: https://orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools
|
ORCHARD_TEST_URL: https://orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools
|
||||||
ORCHARD_TEST_PASSWORD: $DEV_ADMIN_PASSWORD
|
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "main"'
|
- if: '$CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "main"'
|
||||||
when: on_success
|
when: on_success
|
||||||
@@ -461,7 +453,6 @@ deploy_feature:
|
|||||||
--namespace $NAMESPACE \
|
--namespace $NAMESPACE \
|
||||||
-f $VALUES_FILE \
|
-f $VALUES_FILE \
|
||||||
--set image.tag=git.linux-amd64-$CI_COMMIT_SHA \
|
--set image.tag=git.linux-amd64-$CI_COMMIT_SHA \
|
||||||
--set orchard.auth.adminPassword=$DEV_ADMIN_PASSWORD \
|
|
||||||
--set ingress.hosts[0].host=orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools \
|
--set ingress.hosts[0].host=orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools \
|
||||||
--set ingress.tls[0].hosts[0]=orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools \
|
--set ingress.tls[0].hosts[0]=orchard-$CI_COMMIT_REF_SLUG.common.global.bsf.tools \
|
||||||
--set ingress.tls[0].secretName=orchard-$CI_COMMIT_REF_SLUG-tls \
|
--set ingress.tls[0].secretName=orchard-$CI_COMMIT_REF_SLUG-tls \
|
||||||
|
|||||||
@@ -7,15 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- Added `ORCHARD_ADMIN_PASSWORD` environment variable to configure initial admin password (#87)
|
|
||||||
- When set, admin user is created with the specified password (no password change required)
|
|
||||||
- When not set, defaults to `changeme123` and requires password change on first login
|
|
||||||
- Added Helm chart support for admin password via multiple sources (#87):
|
|
||||||
- `orchard.auth.adminPassword` - plain value (creates K8s secret)
|
|
||||||
- `orchard.auth.existingSecret` - reference existing K8s secret
|
|
||||||
- `orchard.auth.secretsManager` - AWS Secrets Manager integration
|
|
||||||
- Added `.env.example` template for local development (#87)
|
|
||||||
- Added `.env` file support in docker-compose.local.yml (#87)
|
|
||||||
- Added Project Settings page accessible to project admins (#65)
|
- Added Project Settings page accessible to project admins (#65)
|
||||||
- General settings section for editing description and visibility
|
- General settings section for editing description and visibility
|
||||||
- Access Management section (moved from project page)
|
- Access Management section (moved from project page)
|
||||||
|
|||||||
@@ -360,36 +360,21 @@ def create_default_admin(db: Session) -> Optional[User]:
|
|||||||
"""Create the default admin user if no users exist.
|
"""Create the default admin user if no users exist.
|
||||||
|
|
||||||
Returns the created user, or None if users already exist.
|
Returns the created user, or None if users already exist.
|
||||||
|
|
||||||
The admin password can be set via ORCHARD_ADMIN_PASSWORD environment variable.
|
|
||||||
If not set, defaults to 'changeme123' and requires password change on first login.
|
|
||||||
"""
|
"""
|
||||||
# Check if any users exist
|
# Check if any users exist
|
||||||
user_count = db.query(User).count()
|
user_count = db.query(User).count()
|
||||||
if user_count > 0:
|
if user_count > 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
settings = get_settings()
|
|
||||||
|
|
||||||
# Use configured password or default
|
|
||||||
password = settings.admin_password if settings.admin_password else "changeme123"
|
|
||||||
# Only require password change if using the default password
|
|
||||||
must_change = not settings.admin_password
|
|
||||||
|
|
||||||
# Create default admin
|
# Create default admin
|
||||||
auth_service = AuthService(db)
|
auth_service = AuthService(db)
|
||||||
admin = auth_service.create_user(
|
admin = auth_service.create_user(
|
||||||
username="admin",
|
username="admin",
|
||||||
password=password,
|
password="changeme123",
|
||||||
is_admin=True,
|
is_admin=True,
|
||||||
must_change_password=must_change,
|
must_change_password=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if settings.admin_password:
|
|
||||||
logger.info("Created default admin user with configured password")
|
|
||||||
else:
|
|
||||||
logger.info("Created default admin user with default password (changeme123)")
|
|
||||||
|
|
||||||
return admin
|
return admin
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ class Settings(BaseSettings):
|
|||||||
log_level: str = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
log_level: str = "INFO" # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||||
log_format: str = "auto" # "json", "standard", or "auto" (json in production)
|
log_format: str = "auto" # "json", "standard", or "auto" (json in production)
|
||||||
|
|
||||||
# Initial admin user settings
|
|
||||||
admin_password: str = "" # Initial admin password (if empty, uses 'changeme123')
|
|
||||||
|
|
||||||
# JWT Authentication settings (optional, for external identity providers)
|
# JWT Authentication settings (optional, for external identity providers)
|
||||||
jwt_enabled: bool = False # Enable JWT token validation
|
jwt_enabled: bool = False # Enable JWT token validation
|
||||||
jwt_secret: str = "" # Secret key for HS256, or leave empty for RS256 with JWKS
|
jwt_secret: str = "" # Secret key for HS256, or leave empty for RS256 with JWKS
|
||||||
|
|||||||
@@ -56,27 +56,6 @@ os.environ.setdefault("ORCHARD_S3_BUCKET", "test-bucket")
|
|||||||
os.environ.setdefault("ORCHARD_S3_ACCESS_KEY_ID", "test")
|
os.environ.setdefault("ORCHARD_S3_ACCESS_KEY_ID", "test")
|
||||||
os.environ.setdefault("ORCHARD_S3_SECRET_ACCESS_KEY", "test")
|
os.environ.setdefault("ORCHARD_S3_SECRET_ACCESS_KEY", "test")
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
|
||||||
# Admin Credentials Helper
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
|
|
||||||
def get_admin_password() -> str:
|
|
||||||
"""Get the admin password for test authentication.
|
|
||||||
|
|
||||||
Returns the password from ORCHARD_TEST_PASSWORD environment variable,
|
|
||||||
or 'changeme123' as the default for local development.
|
|
||||||
"""
|
|
||||||
# Use 'or' to handle empty string (when CI variable is undefined)
|
|
||||||
return os.environ.get("ORCHARD_TEST_PASSWORD") or "changeme123"
|
|
||||||
|
|
||||||
|
|
||||||
def get_admin_username() -> str:
|
|
||||||
"""Get the admin username for test authentication."""
|
|
||||||
return os.environ.get("ORCHARD_TEST_USERNAME") or "admin"
|
|
||||||
|
|
||||||
|
|
||||||
# Re-export factory functions for backward compatibility
|
# Re-export factory functions for backward compatibility
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
create_test_file,
|
create_test_file,
|
||||||
@@ -249,9 +228,9 @@ def integration_client():
|
|||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
# Connect to the running orchard-server container or deployed environment
|
# Connect to the running orchard-server container or deployed environment
|
||||||
base_url = os.environ.get("ORCHARD_TEST_URL") or "http://localhost:8080"
|
base_url = os.environ.get("ORCHARD_TEST_URL", "http://localhost:8080")
|
||||||
username = get_admin_username()
|
username = os.environ.get("ORCHARD_TEST_USERNAME", "admin")
|
||||||
password = get_admin_password()
|
password = os.environ.get("ORCHARD_TEST_PASSWORD", "changeme123")
|
||||||
|
|
||||||
with httpx.Client(base_url=base_url, timeout=30.0) as client:
|
with httpx.Client(base_url=base_url, timeout=30.0) as client:
|
||||||
# Login as admin to enable write operations
|
# Login as admin to enable write operations
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ allow these tests to run. Production uses strict rate limits (5/minute).
|
|||||||
import pytest
|
import pytest
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from tests.conftest import get_admin_password, get_admin_username
|
|
||||||
|
|
||||||
|
|
||||||
# Mark all tests in this module as auth_intensive (informational, not excluded from CI)
|
# Mark all tests in this module as auth_intensive (informational, not excluded from CI)
|
||||||
pytestmark = pytest.mark.auth_intensive
|
pytestmark = pytest.mark.auth_intensive
|
||||||
@@ -23,11 +21,11 @@ class TestAuthLogin:
|
|||||||
"""Test successful login with default admin credentials."""
|
"""Test successful login with default admin credentials."""
|
||||||
response = auth_client.post(
|
response = auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["username"] == get_admin_username()
|
assert data["username"] == "admin"
|
||||||
assert data["is_admin"] is True
|
assert data["is_admin"] is True
|
||||||
assert "orchard_session" in response.cookies
|
assert "orchard_session" in response.cookies
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ class TestAuthLogin:
|
|||||||
"""Test login with wrong password."""
|
"""Test login with wrong password."""
|
||||||
response = auth_client.post(
|
response = auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": "wrongpassword"},
|
json={"username": "admin", "password": "wrongpassword"},
|
||||||
)
|
)
|
||||||
assert response.status_code == 401
|
assert response.status_code == 401
|
||||||
assert "Invalid username or password" in response.json()["detail"]
|
assert "Invalid username or password" in response.json()["detail"]
|
||||||
@@ -60,7 +58,7 @@ class TestAuthLogout:
|
|||||||
# First login
|
# First login
|
||||||
login_response = auth_client.post(
|
login_response = auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
assert login_response.status_code == 200
|
assert login_response.status_code == 200
|
||||||
|
|
||||||
@@ -86,13 +84,13 @@ class TestAuthMe:
|
|||||||
# Login first
|
# Login first
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
response = auth_client.get("/api/v1/auth/me")
|
response = auth_client.get("/api/v1/auth/me")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["username"] == get_admin_username()
|
assert data["username"] == "admin"
|
||||||
assert data["is_admin"] is True
|
assert data["is_admin"] is True
|
||||||
assert "id" in data
|
assert "id" in data
|
||||||
assert "created_at" in data
|
assert "created_at" in data
|
||||||
@@ -121,7 +119,7 @@ class TestAuthChangePassword:
|
|||||||
# Login as admin to create a test user
|
# Login as admin to create a test user
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
test_username = f"pwchange_{uuid4().hex[:8]}"
|
test_username = f"pwchange_{uuid4().hex[:8]}"
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
@@ -164,7 +162,7 @@ class TestAuthChangePassword:
|
|||||||
# Login as admin to create a test user
|
# Login as admin to create a test user
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
test_username = f"pwwrong_{uuid4().hex[:8]}"
|
test_username = f"pwwrong_{uuid4().hex[:8]}"
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
@@ -196,7 +194,7 @@ class TestAPIKeys:
|
|||||||
# Login first
|
# Login first
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create API key
|
# Create API key
|
||||||
@@ -228,7 +226,7 @@ class TestAPIKeys:
|
|||||||
# Login and create API key
|
# Login and create API key
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
create_response = auth_client.post(
|
create_response = auth_client.post(
|
||||||
"/api/v1/auth/keys",
|
"/api/v1/auth/keys",
|
||||||
@@ -244,12 +242,12 @@ class TestAPIKeys:
|
|||||||
headers={"Authorization": f"Bearer {api_key}"},
|
headers={"Authorization": f"Bearer {api_key}"},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["username"] == get_admin_username()
|
assert response.json()["username"] == "admin"
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
auth_client.delete(f"/api/v1/auth/keys/{key_id}")
|
auth_client.delete(f"/api/v1/auth/keys/{key_id}")
|
||||||
|
|
||||||
@@ -259,7 +257,7 @@ class TestAPIKeys:
|
|||||||
# Login and create API key
|
# Login and create API key
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
create_response = auth_client.post(
|
create_response = auth_client.post(
|
||||||
"/api/v1/auth/keys",
|
"/api/v1/auth/keys",
|
||||||
@@ -290,14 +288,14 @@ class TestAdminUserManagement:
|
|||||||
# Login as admin
|
# Login as admin
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
response = auth_client.get("/api/v1/admin/users")
|
response = auth_client.get("/api/v1/admin/users")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
users = response.json()
|
users = response.json()
|
||||||
assert len(users) >= 1
|
assert len(users) >= 1
|
||||||
assert any(u["username"] == get_admin_username() for u in users)
|
assert any(u["username"] == "admin" for u in users)
|
||||||
|
|
||||||
@pytest.mark.integration
|
@pytest.mark.integration
|
||||||
def test_create_user(self, auth_client):
|
def test_create_user(self, auth_client):
|
||||||
@@ -305,7 +303,7 @@ class TestAdminUserManagement:
|
|||||||
# Login as admin
|
# Login as admin
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create new user
|
# Create new user
|
||||||
@@ -338,7 +336,7 @@ class TestAdminUserManagement:
|
|||||||
# Login as admin
|
# Login as admin
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a test user
|
# Create a test user
|
||||||
@@ -364,7 +362,7 @@ class TestAdminUserManagement:
|
|||||||
# Login as admin
|
# Login as admin
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a test user
|
# Create a test user
|
||||||
@@ -395,7 +393,7 @@ class TestAdminUserManagement:
|
|||||||
# Login as admin and create non-admin user
|
# Login as admin and create non-admin user
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
test_username = f"nonadmin_{uuid4().hex[:8]}"
|
test_username = f"nonadmin_{uuid4().hex[:8]}"
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
@@ -425,7 +423,7 @@ class TestSecurityEdgeCases:
|
|||||||
# Login as admin and create a user
|
# Login as admin and create a user
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
test_username = f"inactive_{uuid4().hex[:8]}"
|
test_username = f"inactive_{uuid4().hex[:8]}"
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
@@ -453,7 +451,7 @@ class TestSecurityEdgeCases:
|
|||||||
"""Test that short passwords are rejected when creating users."""
|
"""Test that short passwords are rejected when creating users."""
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
response = auth_client.post(
|
response = auth_client.post(
|
||||||
@@ -469,7 +467,7 @@ class TestSecurityEdgeCases:
|
|||||||
# Create test user
|
# Create test user
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
test_username = f"shortchange_{uuid4().hex[:8]}"
|
test_username = f"shortchange_{uuid4().hex[:8]}"
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
@@ -496,7 +494,7 @@ class TestSecurityEdgeCases:
|
|||||||
"""Test that short passwords are rejected when resetting password."""
|
"""Test that short passwords are rejected when resetting password."""
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a test user first
|
# Create a test user first
|
||||||
@@ -518,7 +516,7 @@ class TestSecurityEdgeCases:
|
|||||||
"""Test that duplicate usernames are rejected."""
|
"""Test that duplicate usernames are rejected."""
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
|
|
||||||
test_username = f"duplicate_{uuid4().hex[:8]}"
|
test_username = f"duplicate_{uuid4().hex[:8]}"
|
||||||
@@ -543,7 +541,7 @@ class TestSecurityEdgeCases:
|
|||||||
# Login as admin and create an API key
|
# Login as admin and create an API key
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
create_response = auth_client.post(
|
create_response = auth_client.post(
|
||||||
"/api/v1/auth/keys",
|
"/api/v1/auth/keys",
|
||||||
@@ -574,7 +572,7 @@ class TestSecurityEdgeCases:
|
|||||||
auth_client.cookies.clear()
|
auth_client.cookies.clear()
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
auth_client.delete(f"/api/v1/auth/keys/{admin_key_id}")
|
auth_client.delete(f"/api/v1/auth/keys/{admin_key_id}")
|
||||||
|
|
||||||
@@ -584,7 +582,7 @@ class TestSecurityEdgeCases:
|
|||||||
# Create a test user
|
# Create a test user
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
json={"username": get_admin_username(), "password": get_admin_password()},
|
json={"username": "admin", "password": "changeme123"},
|
||||||
)
|
)
|
||||||
test_username = f"sessiontest_{uuid4().hex[:8]}"
|
test_username = f"sessiontest_{uuid4().hex[:8]}"
|
||||||
auth_client.post(
|
auth_client.post(
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
"""Unit tests for authentication module."""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import patch, MagicMock
|
|
||||||
|
|
||||||
|
|
||||||
class TestCreateDefaultAdmin:
|
|
||||||
"""Tests for the create_default_admin function."""
|
|
||||||
|
|
||||||
def test_create_default_admin_with_env_password(self):
|
|
||||||
"""Test that ORCHARD_ADMIN_PASSWORD env var sets admin password."""
|
|
||||||
from app.auth import create_default_admin, verify_password
|
|
||||||
|
|
||||||
# Create mock settings with custom password
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings.admin_password = "my-custom-password-123"
|
|
||||||
|
|
||||||
# Mock database session
|
|
||||||
mock_db = MagicMock()
|
|
||||||
mock_db.query.return_value.count.return_value = 0 # No existing users
|
|
||||||
|
|
||||||
# Track the user that gets created
|
|
||||||
created_user = None
|
|
||||||
|
|
||||||
def capture_user(user):
|
|
||||||
nonlocal created_user
|
|
||||||
created_user = user
|
|
||||||
|
|
||||||
mock_db.add.side_effect = capture_user
|
|
||||||
|
|
||||||
with patch("app.auth.get_settings", return_value=mock_settings):
|
|
||||||
admin = create_default_admin(mock_db)
|
|
||||||
|
|
||||||
# Verify the user was created
|
|
||||||
assert mock_db.add.called
|
|
||||||
assert created_user is not None
|
|
||||||
assert created_user.username == "admin"
|
|
||||||
assert created_user.is_admin is True
|
|
||||||
# Password should NOT require change when set via env var
|
|
||||||
assert created_user.must_change_password is False
|
|
||||||
# Verify password was hashed correctly
|
|
||||||
assert verify_password("my-custom-password-123", created_user.password_hash)
|
|
||||||
|
|
||||||
def test_create_default_admin_with_default_password(self):
|
|
||||||
"""Test that default password 'changeme123' is used when env var not set."""
|
|
||||||
from app.auth import create_default_admin, verify_password
|
|
||||||
|
|
||||||
# Create mock settings with empty password (default)
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings.admin_password = ""
|
|
||||||
|
|
||||||
# Mock database session
|
|
||||||
mock_db = MagicMock()
|
|
||||||
mock_db.query.return_value.count.return_value = 0 # No existing users
|
|
||||||
|
|
||||||
# Track the user that gets created
|
|
||||||
created_user = None
|
|
||||||
|
|
||||||
def capture_user(user):
|
|
||||||
nonlocal created_user
|
|
||||||
created_user = user
|
|
||||||
|
|
||||||
mock_db.add.side_effect = capture_user
|
|
||||||
|
|
||||||
with patch("app.auth.get_settings", return_value=mock_settings):
|
|
||||||
admin = create_default_admin(mock_db)
|
|
||||||
|
|
||||||
# Verify the user was created
|
|
||||||
assert mock_db.add.called
|
|
||||||
assert created_user is not None
|
|
||||||
assert created_user.username == "admin"
|
|
||||||
assert created_user.is_admin is True
|
|
||||||
# Password SHOULD require change when using default
|
|
||||||
assert created_user.must_change_password is True
|
|
||||||
# Verify default password was used
|
|
||||||
assert verify_password("changeme123", created_user.password_hash)
|
|
||||||
|
|
||||||
def test_create_default_admin_skips_when_users_exist(self):
|
|
||||||
"""Test that no admin is created when users already exist."""
|
|
||||||
from app.auth import create_default_admin
|
|
||||||
|
|
||||||
# Create mock settings
|
|
||||||
mock_settings = MagicMock()
|
|
||||||
mock_settings.admin_password = "some-password"
|
|
||||||
|
|
||||||
# Mock database session with existing users
|
|
||||||
mock_db = MagicMock()
|
|
||||||
mock_db.query.return_value.count.return_value = 1 # Users exist
|
|
||||||
|
|
||||||
with patch("app.auth.get_settings", return_value=mock_settings):
|
|
||||||
result = create_default_admin(mock_db)
|
|
||||||
|
|
||||||
# Should return None and not create any user
|
|
||||||
assert result is None
|
|
||||||
assert not mock_db.add.called
|
|
||||||
@@ -26,8 +26,6 @@ services:
|
|||||||
- ORCHARD_REDIS_PORT=6379
|
- ORCHARD_REDIS_PORT=6379
|
||||||
# Higher rate limit for local development/testing
|
# Higher rate limit for local development/testing
|
||||||
- ORCHARD_LOGIN_RATE_LIMIT=1000/minute
|
- ORCHARD_LOGIN_RATE_LIMIT=1000/minute
|
||||||
# Admin password - set in .env file or environment (see .env.example)
|
|
||||||
- ORCHARD_ADMIN_PASSWORD=${ORCHARD_ADMIN_PASSWORD:-}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|||||||
@@ -141,16 +141,3 @@ MinIO secret name
|
|||||||
{{- printf "%s-s3-secret" (include "orchard.fullname" .) }}
|
{{- printf "%s-s3-secret" (include "orchard.fullname" .) }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
|
||||||
Auth secret name (for admin password)
|
|
||||||
*/}}
|
|
||||||
{{- define "orchard.auth.secretName" -}}
|
|
||||||
{{- if and .Values.orchard.auth .Values.orchard.auth.existingSecret }}
|
|
||||||
{{- .Values.orchard.auth.existingSecret }}
|
|
||||||
{{- else if and .Values.orchard.auth .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled }}
|
|
||||||
{{- printf "%s-auth-credentials" (include "orchard.fullname" .) }}
|
|
||||||
{{- else }}
|
|
||||||
{{- printf "%s-auth-secret" (include "orchard.fullname" .) }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -128,27 +128,11 @@ spec:
|
|||||||
value: {{ .Values.orchard.rateLimit.login | quote }}
|
value: {{ .Values.orchard.rateLimit.login | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.orchard.auth }}
|
{{- if and .Values.orchard.database.secretsManager .Values.orchard.database.secretsManager.enabled }}
|
||||||
{{- if or .Values.orchard.auth.secretsManager .Values.orchard.auth.existingSecret .Values.orchard.auth.adminPassword }}
|
|
||||||
- name: ORCHARD_ADMIN_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: {{ include "orchard.auth.secretName" . }}
|
|
||||||
key: admin-password
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- if or (and .Values.orchard.database.secretsManager .Values.orchard.database.secretsManager.enabled) (and .Values.orchard.auth .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled) }}
|
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
{{- if and .Values.orchard.database.secretsManager .Values.orchard.database.secretsManager.enabled }}
|
|
||||||
- name: db-secrets
|
- name: db-secrets
|
||||||
mountPath: /mnt/secrets-store/db
|
mountPath: /mnt/secrets-store
|
||||||
readOnly: true
|
readOnly: true
|
||||||
{{- end }}
|
|
||||||
{{- if and .Values.orchard.auth .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled }}
|
|
||||||
- name: auth-secrets
|
|
||||||
mountPath: /mnt/secrets-store/auth
|
|
||||||
readOnly: true
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
{{- toYaml .Values.livenessProbe | nindent 12 }}
|
{{- toYaml .Values.livenessProbe | nindent 12 }}
|
||||||
@@ -156,24 +140,14 @@ spec:
|
|||||||
{{- toYaml .Values.readinessProbe | nindent 12 }}
|
{{- toYaml .Values.readinessProbe | nindent 12 }}
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
{{- toYaml .Values.resources | nindent 12 }}
|
||||||
{{- if or (and .Values.orchard.database.secretsManager .Values.orchard.database.secretsManager.enabled) (and .Values.orchard.auth .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled) }}
|
{{- if and .Values.orchard.database.secretsManager .Values.orchard.database.secretsManager.enabled }}
|
||||||
volumes:
|
volumes:
|
||||||
{{- if and .Values.orchard.database.secretsManager .Values.orchard.database.secretsManager.enabled }}
|
|
||||||
- name: db-secrets
|
- name: db-secrets
|
||||||
csi:
|
csi:
|
||||||
driver: secrets-store.csi.k8s.io
|
driver: secrets-store.csi.k8s.io
|
||||||
readOnly: true
|
readOnly: true
|
||||||
volumeAttributes:
|
volumeAttributes:
|
||||||
secretProviderClass: {{ include "orchard.fullname" . }}-db-secret
|
secretProviderClass: {{ include "orchard.fullname" . }}-db-secret
|
||||||
{{- end }}
|
|
||||||
{{- if and .Values.orchard.auth .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled }}
|
|
||||||
- name: auth-secrets
|
|
||||||
csi:
|
|
||||||
driver: secrets-store.csi.k8s.io
|
|
||||||
readOnly: true
|
|
||||||
volumeAttributes:
|
|
||||||
secretProviderClass: {{ include "orchard.fullname" . }}-auth-secret
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- with .Values.nodeSelector }}
|
{{- with .Values.nodeSelector }}
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
|
|||||||
@@ -25,27 +25,3 @@ spec:
|
|||||||
- objectName: db-password
|
- objectName: db-password
|
||||||
key: password
|
key: password
|
||||||
{{- end }}
|
{{- end }}
|
||||||
---
|
|
||||||
{{- if and .Values.orchard.auth .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled }}
|
|
||||||
apiVersion: secrets-store.csi.x-k8s.io/v1
|
|
||||||
kind: SecretProviderClass
|
|
||||||
metadata:
|
|
||||||
name: {{ include "orchard.fullname" . }}-auth-secret
|
|
||||||
labels:
|
|
||||||
{{- include "orchard.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
provider: aws
|
|
||||||
parameters:
|
|
||||||
objects: |
|
|
||||||
- objectName: "{{ .Values.orchard.auth.secretsManager.secretArn }}"
|
|
||||||
objectType: "secretsmanager"
|
|
||||||
jmesPath:
|
|
||||||
- path: admin_password
|
|
||||||
objectAlias: admin-password
|
|
||||||
secretObjects:
|
|
||||||
- secretName: {{ include "orchard.fullname" . }}-auth-credentials
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
- objectName: admin-password
|
|
||||||
key: admin-password
|
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -22,15 +22,3 @@ data:
|
|||||||
access-key-id: {{ .Values.orchard.s3.accessKeyId | b64enc | quote }}
|
access-key-id: {{ .Values.orchard.s3.accessKeyId | b64enc | quote }}
|
||||||
secret-access-key: {{ .Values.orchard.s3.secretAccessKey | b64enc | quote }}
|
secret-access-key: {{ .Values.orchard.s3.secretAccessKey | b64enc | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
---
|
|
||||||
{{- if and .Values.orchard.auth .Values.orchard.auth.adminPassword (not .Values.orchard.auth.existingSecret) (not (and .Values.orchard.auth.secretsManager .Values.orchard.auth.secretsManager.enabled)) }}
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: {{ include "orchard.fullname" . }}-auth-secret
|
|
||||||
labels:
|
|
||||||
{{- include "orchard.labels" . | nindent 4 }}
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
admin-password: {{ .Values.orchard.auth.adminPassword | b64enc | quote }}
|
|
||||||
{{- end }}
|
|
||||||
|
|||||||
@@ -90,10 +90,6 @@ orchard:
|
|||||||
host: "0.0.0.0"
|
host: "0.0.0.0"
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
# Authentication settings
|
|
||||||
# Admin password is set via CI variable (DEV_ADMIN_PASSWORD) passed as --set flag
|
|
||||||
# This keeps the password out of version control
|
|
||||||
|
|
||||||
database:
|
database:
|
||||||
host: ""
|
host: ""
|
||||||
port: 5432
|
port: 5432
|
||||||
|
|||||||
@@ -93,13 +93,6 @@ orchard:
|
|||||||
host: "0.0.0.0"
|
host: "0.0.0.0"
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
# Authentication settings
|
|
||||||
auth:
|
|
||||||
# Admin password from AWS Secrets Manager
|
|
||||||
secretsManager:
|
|
||||||
enabled: true
|
|
||||||
secretArn: "arn:aws-us-gov:secretsmanager:us-gov-west-1:052673043337:secret:orch-prod-creds-0nhqkY"
|
|
||||||
|
|
||||||
# Database configuration - uses AWS Secrets Manager via CSI driver
|
# Database configuration - uses AWS Secrets Manager via CSI driver
|
||||||
database:
|
database:
|
||||||
host: "orchard-prd.cluster-cvw3jzjkozoc.us-gov-west-1.rds.amazonaws.com"
|
host: "orchard-prd.cluster-cvw3jzjkozoc.us-gov-west-1.rds.amazonaws.com"
|
||||||
|
|||||||
@@ -95,13 +95,6 @@ orchard:
|
|||||||
host: "0.0.0.0"
|
host: "0.0.0.0"
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
# Authentication settings
|
|
||||||
auth:
|
|
||||||
# Admin password from AWS Secrets Manager
|
|
||||||
secretsManager:
|
|
||||||
enabled: true
|
|
||||||
secretArn: "arn:aws-us-gov:secretsmanager:us-gov-west-1:052673043337:secret:orchard-stage-creds-SMqvQx"
|
|
||||||
|
|
||||||
# Database configuration - uses AWS Secrets Manager via CSI driver
|
# Database configuration - uses AWS Secrets Manager via CSI driver
|
||||||
database:
|
database:
|
||||||
host: "orchard-stage.cluster-cvw3jzjkozoc.us-gov-west-1.rds.amazonaws.com"
|
host: "orchard-stage.cluster-cvw3jzjkozoc.us-gov-west-1.rds.amazonaws.com"
|
||||||
|
|||||||
@@ -120,17 +120,6 @@ orchard:
|
|||||||
mode: "presigned" # presigned, redirect, or proxy
|
mode: "presigned" # presigned, redirect, or proxy
|
||||||
presignedUrlExpiry: 3600 # Presigned URL expiry in seconds
|
presignedUrlExpiry: 3600 # Presigned URL expiry in seconds
|
||||||
|
|
||||||
# Authentication settings
|
|
||||||
auth:
|
|
||||||
# Option 1: Plain admin password (creates K8s secret)
|
|
||||||
adminPassword: ""
|
|
||||||
# Option 2: Use existing K8s secret (must have 'admin-password' key)
|
|
||||||
existingSecret: ""
|
|
||||||
# Option 3: AWS Secrets Manager
|
|
||||||
# secretsManager:
|
|
||||||
# enabled: false
|
|
||||||
# secretArn: "" # Secret must have 'admin_password' field
|
|
||||||
|
|
||||||
# PostgreSQL subchart configuration
|
# PostgreSQL subchart configuration
|
||||||
postgresql:
|
postgresql:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
Reference in New Issue
Block a user