diff --git a/README.md b/README.md index 473a1397..28402e83 100644 --- a/README.md +++ b/README.md @@ -324,44 +324,39 @@ When you switch from Mode A (self-signed cert) to Mode B (Let's Encrypt) using t ## TLS Certificate Auto-Renewal (Mode B) -When running in Mode B (Let's Encrypt), certbot renews the certificate automatically via `systemd certbot.timer` (twice daily). The certificate is renewed 30 days before expiry. +Let's Encrypt certificates are valid for 90 days. The certificate is stored in the container volume `conf/docker/container-volume/mc-iam-manager/certs` (mounted into `mc-iam-manager-nginx`), **not** in the host's `/etc/letsencrypt`, so a host certbot or `certbot.timer` does not renew it. -### Webroot setup (required once after installation) +`conf/docker/scripts/renew-cert.sh` renews it with the certbot container: +- `mc-iam-manager-nginx` running → **webroot** (nginx keeps serving `/.well-known/acme-challenge/`, no downtime), then `nginx -s reload` +- `mc-iam-manager-nginx` not running → **standalone** (certbot binds port 80) -Mode B uses the **webroot** authenticator so nginx keeps running during renewal. If your installation used the `standalone` authenticator (older setup), switch it once: +Port 80 must be reachable from the internet for the HTTP-01 challenge. -```shell -sudo certbot certonly \ - --webroot \ - -w /conf/docker/container-volume/certbot/www \ - -d \ - --force-renewal -``` +### Automatic renewal (cron) + +`installAll.sh -m prod` registers `/etc/cron.d/mcmp-cert-renew` (twice daily; certbot renews only within 30 days of expiry). Log: `/var/log/mcmp-cert-renew.log`. -Verify the renewal config was updated: +For installations made before this was added, register it once: ```shell -sudo grep "authenticator" /etc/letsencrypt/renewal/.conf -# Expected: authenticator = webroot +sudo conf/docker/scripts/renew-cert.sh --install-cron ``` -### Deploy hook — nginx reload after renewal - -After renewal, the new certificate must be loaded into the running nginx container. Install the deploy hook once: +### Manual renewal / verification ```shell -sudo cp conf/docker/scripts/certbot-deploy-hook.sh \ - /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh -sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh +sudo conf/docker/scripts/renew-cert.sh --dry-run # Expected: "all simulated renewals succeeded" +sudo conf/docker/scripts/renew-cert.sh --force-renewal # renew now (e.g. certificate already expired) ``` -### Verify auto-renewal +Check the served certificate: ```shell -sudo certbot renew --dry-run -# Expected: "all simulated renewals succeeded" +echo | openssl s_client -connect :443 -servername 2>/dev/null | openssl x509 -noout -dates ``` +> If you previously installed `certbot-deploy-hook.sh` or a host `certbot` for this domain, remove them — they do not touch the certificate nginx uses. + --- ## Firewall Port Information diff --git a/README_kr.md b/README_kr.md index 48aaf4cf..56bdf0ca 100644 --- a/README_kr.md +++ b/README_kr.md @@ -267,44 +267,39 @@ Mode A(자체 서명 인증서) → Mode B(Let's Encrypt)로 같은 도메인을 ## TLS 인증서 자동갱신 (Mode B) -Mode B(Let's Encrypt) 실행 시 certbot이 `systemd certbot.timer`를 통해 하루 2회 자동갱신을 시도합니다. 만료 30일 전부터 갱신이 시작됩니다. +Let's Encrypt 인증서의 유효기간은 90일입니다. 인증서는 호스트의 `/etc/letsencrypt`가 **아니라** 컨테이너 볼륨 `conf/docker/container-volume/mc-iam-manager/certs`(`mc-iam-manager-nginx`에 마운트)에 저장되므로, 호스트 certbot이나 `certbot.timer`로는 갱신되지 않습니다. -### Webroot 방식 전환 (최초 1회 필요) +`conf/docker/scripts/renew-cert.sh`가 certbot 컨테이너로 갱신합니다: +- `mc-iam-manager-nginx` 실행 중 → **webroot** 방식(nginx가 `/.well-known/acme-challenge/`를 계속 서빙, 중단 없음) 후 `nginx -s reload` +- `mc-iam-manager-nginx` 미실행 → **standalone** 방식(certbot이 80 포트 사용) -Mode B는 갱신 중에도 nginx가 계속 실행될 수 있도록 **webroot** 인증 방식을 사용합니다. 기존 `standalone` 방식으로 설치된 경우 아래 명령으로 한 번 전환해야 합니다: +HTTP-01 검증을 위해 80 포트가 인터넷에서 접근 가능해야 합니다. -```shell -sudo certbot certonly \ - --webroot \ - -w /conf/docker/container-volume/certbot/www \ - -d \ - --force-renewal -``` +### 자동갱신 (cron) + +`installAll.sh -m prod`가 `/etc/cron.d/mcmp-cert-renew`(하루 2회, 만료 30일 이내일 때만 실제 갱신)를 등록합니다. 로그: `/var/log/mcmp-cert-renew.log` -전환 확인: +이 기능 추가 이전에 설치한 환경은 한 번 등록합니다: ```shell -sudo grep "authenticator" /etc/letsencrypt/renewal/.conf -# 기대값: authenticator = webroot +sudo conf/docker/scripts/renew-cert.sh --install-cron ``` -### Deploy Hook — 갱신 후 nginx 자동 reload - -인증서 갱신 후 nginx 컨테이너에 새 인증서를 즉시 적용하기 위한 deploy hook을 한 번 설치합니다: +### 수동 갱신 / 검증 ```shell -sudo cp conf/docker/scripts/certbot-deploy-hook.sh \ - /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh -sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh +sudo conf/docker/scripts/renew-cert.sh --dry-run # 기대값: "all simulated renewals succeeded" +sudo conf/docker/scripts/renew-cert.sh --force-renewal # 즉시 갱신 (예: 이미 만료된 경우) ``` -### 자동갱신 검증 +적용된 인증서 확인: ```shell -sudo certbot renew --dry-run -# 기대값: "all simulated renewals succeeded" +echo | openssl s_client -connect :443 -servername 2>/dev/null | openssl x509 -noout -dates ``` +> 이전 안내에 따라 `certbot-deploy-hook.sh`나 호스트 `certbot`을 이 도메인용으로 설치했다면 제거하세요. nginx가 사용하는 인증서에는 영향을 주지 않습니다. + --- ## 방화벽 포트 정보 diff --git a/bin/installAll.sh b/bin/installAll.sh index 6dc96ebe..df13c467 100755 --- a/bin/installAll.sh +++ b/bin/installAll.sh @@ -467,6 +467,12 @@ case $IAM_MODE in echo "✓ Certificate generation completed." # Reclaim ownership of volume directories created by the certbot container (runs as root) sudo chown -R "$USER:$USER" ../conf/docker/container-volume + # Register automatic renewal (the certificate is valid for 90 days) + if sudo ../conf/docker/scripts/renew-cert.sh --install-cron; then + echo "✓ Certificate auto-renewal registered (/etc/cron.d/mcmp-cert-renew)." + else + echo "⚠ Failed to register certificate auto-renewal. Run manually: sudo ../conf/docker/scripts/renew-cert.sh --install-cron" + fi else echo "❌ Error occurred during certificate generation." exit 1 diff --git a/conf/api.yaml b/conf/api.yaml index 9778252a..d12c18a9 100644 --- a/conf/api.yaml +++ b/conf/api.yaml @@ -11,7 +11,7 @@ services: password: mc-iam-manager: - version: 0.7.1 + version: 0.7.2 baseurl: http://mc-iam-manager:5000 auth: type: bearer @@ -25,7 +25,7 @@ services: password: spider mc-web-console: - version: 0.7.1 + version: 0.7.2 baseurl: http://mc-web-console:3000 auth: type: bearer diff --git a/conf/docker/api.yaml b/conf/docker/api.yaml index 9778252a..d12c18a9 100644 --- a/conf/docker/api.yaml +++ b/conf/docker/api.yaml @@ -11,7 +11,7 @@ services: password: mc-iam-manager: - version: 0.7.1 + version: 0.7.2 baseurl: http://mc-iam-manager:5000 auth: type: bearer @@ -25,7 +25,7 @@ services: password: spider mc-web-console: - version: 0.7.1 + version: 0.7.2 baseurl: http://mc-web-console:3000 auth: type: bearer diff --git a/conf/docker/conf/mc-iam-manager/permission.yaml b/conf/docker/conf/mc-iam-manager/permission.yaml index 22454133..2c68c091 100644 --- a/conf/docker/conf/mc-iam-manager/permission.yaml +++ b/conf/docker/conf/mc-iam-manager/permission.yaml @@ -29,6 +29,7 @@ permissions: - connections - clouddrivers - cspaccounts + - cspdefinitions - cloudresources - serverspecs - serverimages diff --git a/conf/docker/conf/mc-web-console/api/conf/api.yaml b/conf/docker/conf/mc-web-console/api/conf/api.yaml index ed86e177..fe169b98 100644 --- a/conf/docker/conf/mc-web-console/api/conf/api.yaml +++ b/conf/docker/conf/mc-web-console/api/conf/api.yaml @@ -11,7 +11,7 @@ services: password: mc-iam-manager: - version: 0.7.1 + version: 0.7.2 baseurl: http://mc-iam-manager:5000 auth: type: bearer @@ -25,7 +25,7 @@ services: password: default mc-web-console: - version: 0.7.1 + version: 0.7.2 baseurl: http://mc-web-console-api:3000 auth: type: bearer @@ -946,6 +946,10 @@ serviceActions: method: get resourcePath: /api/auth/temp-credential-csps description: 임시 자격 증명을 지원하는 CSP 목록 조회 + GetTemporaryCredentials: + method: post + resourcePath: /api/workspaces/temporary-credentials + description: "로그인 사용자의 워크스페이스 역할에 매핑된 CSP Role로 CSP 임시 자격 증명 발급. body: {workspaceId, cspType, region, authMethod?}" Appendresourcepermissionpolicesbyoperationid: method: put resourcePath: /api/permission/framewrok/{framework}/operationid/{operationid}/append @@ -1118,6 +1122,10 @@ serviceActions: method: put resourcePath: /api/users/me/password description: 사용자 본인 비밀번호 변경 + GetMyWorkspaceRoles: + method: get + resourcePath: /api/users/me/workspace-roles + description: 로그인 사용자 본인의 워크스페이스별 역할 목록 Createorganization: method: post resourcePath: /api/organizations diff --git a/conf/docker/conf/mc-web-console/api/conf/webconsole_menu_resources.yaml b/conf/docker/conf/mc-web-console/api/conf/webconsole_menu_resources.yaml index 21bc940a..38321f62 100644 --- a/conf/docker/conf/mc-web-console/api/conf/webconsole_menu_resources.yaml +++ b/conf/docker/conf/mc-web-console/api/conf/webconsole_menu_resources.yaml @@ -120,6 +120,14 @@ menus: priority: 4 menunumber: 1205 + - id: cspdefinitions + parentid: cloudsps + displayname: CSP Definitions + restype: menu + isaction: true + priority: 6 + menunumber: 1207 + - id: cspaccounts parentid: cloudsps displayname: CSP Accounts diff --git a/conf/docker/docker-compose.cert.yaml b/conf/docker/docker-compose.cert.yaml index 28dc211c..95567291 100644 --- a/conf/docker/docker-compose.cert.yaml +++ b/conf/docker/docker-compose.cert.yaml @@ -1,7 +1,8 @@ services: - # Certbot (for certificate issuance and renewal) - # This container is run once on demand. + # Certbot (initial certificate issuance) + # Run once by installAll.sh -m prod before the stack starts, so it uses standalone mode. + # Renewal is handled by scripts/renew-cert.sh (cron), which switches to webroot while nginx is running. mcmp-certbot: image: certbot/certbot:latest container_name: mcmp-certbot @@ -10,6 +11,8 @@ services: volumes: # Stores Certbot config and issued certificates (read/write access required) - ./container-volume/mc-iam-manager/certs:/etc/letsencrypt + # Same webroot that mc-iam-manager-nginx serves for /.well-known/acme-challenge/ + - ./container-volume/certbot/www:/var/www/certbot environment: MC_IAM_MANAGER_PUBLIC_DOMAIN: ${MC_IAM_MANAGER_PUBLIC_DOMAIN} MC_IAM_MANAGER_CERT_EMAIL: ${MC_IAM_MANAGER_CERT_EMAIL} diff --git a/conf/docker/docker-compose.mini.yaml b/conf/docker/docker-compose.mini.yaml index d26bc1c8..70cca6f1 100644 --- a/conf/docker/docker-compose.mini.yaml +++ b/conf/docker/docker-compose.mini.yaml @@ -221,7 +221,7 @@ services: mc-iam-manager: container_name: mc-iam-manager - image: cloudbaristaorg/mc-iam-manager:0.7.1 + image: cloudbaristaorg/mc-iam-manager:0.7.2 restart: unless-stopped networks: - mc-iam-manager-network @@ -389,7 +389,7 @@ services: exec docker-entrypoint.sh postgres" mc-web-console-api: - image: cloudbaristaorg/mc-web-console-api:0.7.1 + image: cloudbaristaorg/mc-web-console-api:0.7.2 pull_policy: always container_name: mc-web-console-api platform: linux/amd64 @@ -438,7 +438,7 @@ services: <<: *default-health-check mc-web-console-front: - image: cloudbaristaorg/mc-web-console-front:0.7.1 + image: cloudbaristaorg/mc-web-console-front:0.7.2 pull_policy: always container_name: mc-web-console-front platform: linux/amd64 diff --git a/conf/docker/docker-compose.yaml b/conf/docker/docker-compose.yaml index 19fe91ad..481d4ab6 100644 --- a/conf/docker/docker-compose.yaml +++ b/conf/docker/docker-compose.yaml @@ -278,7 +278,7 @@ services: mc-iam-manager: container_name: mc-iam-manager - image: cloudbaristaorg/mc-iam-manager:0.7.1 + image: cloudbaristaorg/mc-iam-manager:0.7.2 restart: unless-stopped networks: - mc-iam-manager-network @@ -1187,7 +1187,7 @@ services: exec docker-entrypoint.sh postgres" mc-web-console-api: - image: cloudbaristaorg/mc-web-console-api:0.7.1 + image: cloudbaristaorg/mc-web-console-api:0.7.2 pull_policy: always container_name: mc-web-console-api platform: linux/amd64 @@ -1231,7 +1231,7 @@ services: <<: *default-health-check mc-web-console-front: - image: cloudbaristaorg/mc-web-console-front:0.7.1 + image: cloudbaristaorg/mc-web-console-front:0.7.2 pull_policy: always container_name: mc-web-console-front platform: linux/amd64 diff --git a/conf/docker/scripts/certbot-deploy-hook.sh b/conf/docker/scripts/certbot-deploy-hook.sh deleted file mode 100644 index 47f0d877..00000000 --- a/conf/docker/scripts/certbot-deploy-hook.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# certbot deploy hook — reload mc-iam-manager-nginx after certificate renewal. -# -# Install once: -# sudo cp conf/docker/scripts/certbot-deploy-hook.sh \ -# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh -# sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx-docker.sh -docker exec mc-iam-manager-nginx nginx -s reload 2>/dev/null \ - && echo "[certbot-deploy] mc-iam-manager-nginx reloaded successfully" \ - || echo "[certbot-deploy] WARNING: failed to reload mc-iam-manager-nginx (container may not be running)" diff --git a/conf/docker/scripts/renew-cert.sh b/conf/docker/scripts/renew-cert.sh new file mode 100755 index 00000000..bbce15e8 --- /dev/null +++ b/conf/docker/scripts/renew-cert.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# Renew the Let's Encrypt certificate used by mc-iam-manager-nginx (Mode B). +# +# The certificate lives in the container volume (container-volume/mc-iam-manager/certs), +# not in the host's /etc/letsencrypt, so a host certbot/certbot.timer does not renew it. +# This script runs the certbot container against that volume: +# - nginx running -> webroot (nginx keeps serving /.well-known/acme-challenge/) +# - nginx not running -> standalone (certbot binds port 80 itself) +# and reloads nginx afterwards. +# +# Usage: +# sudo ./renew-cert.sh # renew if due (within 30 days of expiry) +# sudo ./renew-cert.sh --dry-run # test against the staging server +# sudo ./renew-cert.sh --force-renewal # renew now +# sudo ./renew-cert.sh --install-cron # register twice-daily renewal in /etc/cron.d +# Extra arguments are passed to "certbot renew". + +set -u + +SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")" +DOCKER_DIR="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)" +CERT_DIR="$DOCKER_DIR/container-volume/mc-iam-manager/certs" +WEBROOT_DIR="$DOCKER_DIR/container-volume/certbot/www" +NGINX_CONTAINER="mc-iam-manager-nginx" +CERTBOT_IMAGE="certbot/certbot:latest" +CRON_FILE="/etc/cron.d/mcmp-cert-renew" +LOG_FILE="/var/log/mcmp-cert-renew.log" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [renew-cert] $*" +} + +install_cron() { + if [ ! -d /etc/cron.d ]; then + log "WARNING: /etc/cron.d not found. Schedule this manually (twice daily): $SCRIPT_PATH" + return 0 + fi + if [ "$(id -u)" -ne 0 ]; then + log "ERROR: --install-cron requires root (run with sudo)." + return 1 + fi + cat > "$CRON_FILE" <> $LOG_FILE 2>&1 +EOF + chmod 644 "$CRON_FILE" + log "Installed $CRON_FILE (log: $LOG_FILE)" +} + +if [ "${1:-}" = "--install-cron" ]; then + install_cron + exit $? +fi + +if [ ! -d "$CERT_DIR/renewal" ]; then + log "ERROR: no certbot renewal config in $CERT_DIR. Issue the certificate first (installAll.sh -m prod)." + exit 1 +fi + +mkdir -p "$WEBROOT_DIR" + +if docker ps --format '{{.Names}}' | grep -qx "$NGINX_CONTAINER"; then + log "$NGINX_CONTAINER is running - renewing with webroot" + docker run --rm \ + -v "$CERT_DIR:/etc/letsencrypt" \ + -v "$WEBROOT_DIR:/var/www/certbot" \ + "$CERTBOT_IMAGE" renew --non-interactive --webroot -w /var/www/certbot "$@" + rc=$? + if [ $rc -eq 0 ]; then + docker exec "$NGINX_CONTAINER" nginx -s reload \ + && log "$NGINX_CONTAINER reloaded" \ + || log "WARNING: failed to reload $NGINX_CONTAINER" + fi +else + log "$NGINX_CONTAINER is not running - renewing with standalone (port 80)" + docker run --rm -p 80:80 \ + -v "$CERT_DIR:/etc/letsencrypt" \ + "$CERTBOT_IMAGE" renew --non-interactive --standalone "$@" + rc=$? +fi + +# certbot runs as root; give the volume back to its owner (see cleanAll.sh) +if [ "$(id -u)" -eq 0 ]; then + chown -R "$(stat -c '%u:%g' "$DOCKER_DIR")" "$CERT_DIR" "$WEBROOT_DIR" 2>/dev/null || true +fi + +if [ $rc -ne 0 ]; then + log "ERROR: certbot renew failed (exit $rc)" +fi +exit $rc