外部公開していないオンプレのGitLabとRedmineをLet's Encryptの証明書で暗号化する
このサイトでよくGitLabやRedmine関連の構築を書いていますが、
関連するツールはどれもHTTPSの暗号化通信前提の作りになっているんですよね。
そこで自己証明書でHTTPS化しているのですが、以下のエラーに毎度悩まされます。
x509: certificate signed by unknown authority
と言うわけでLet’s Encrypt証明書に変更して
自己証明書を卒業したいと思います!
でも検証目的のサーバーなので外部公開したくない。
なのでHTTP通信が不要なDNS認証を利用して証明書を発行します。
必要なもの
- ドメイン
お名前.comとかで買えるやつです。
本ブログで言うとtsuchinokometal.comですね。 - DNSサーバー
自動更新したいならAPIが使えるAWSやGCPがおすすめですね。
CertbotのDNSプラグインが使えるとさらに楽です。
今回は検証環境を想定して自動更新については書きません. - サーバーとインターネット環境
ドメインやDNSサーバーは無料のサービスもあるようなので、
それらを使えばお金を使わずに構築できるかも?
構成
以前
GitLabとRedmineの連携で、
自己証明書でのHTTPSをNGINXのリバースプロキシで受ける構成を書いたので、
今回はその自己証明書を入れ替えようと思います。
前回と同じくOSはCentOS7です。
[centos@example ~]$ cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
Certbotのインストール
CertbotはLet’s Encrypt証明書を取得するためのツールです。
まずはこちら
を参考にCertbotをインストールしてください。
ご自身の環境に合わせて選択してください。
snapを使ったインストールを推奨しているようなので
こちら
を参考にまずsnapをインストールします。
UbuntuにaptでCertbotをインストールしたらDNSプラグインがインストールできなかったので
snapを使った方がいいと思います。
[centos@example ~]$ sudo yum install epel-release
[centos@example ~]$ sudo yum install snapd
[centos@example ~]$ sudo systemctl enable --now snapd.socket
Created symlink from /etc/systemd/system/sockets.target.wants/snapd.socket to /usr/lib/systemd/system/snapd.socket.
[centos@example ~]$ sudo ln -s /var/lib/snapd/snap /snap
Certbotのインストールに戻ります。
僕の場合以下のエラーが発生しましたが、一旦ログインし直すと直りました。
[centos@example ~]$ sudo snap install core; sudo snap refresh core
error: too early for operation, device not yet seeded or device model not acknowledged
error: too early for operation, device not yet seeded or device model not acknowledged
[centos@example ~]$ exit
インストールできました。
[centos@example ~]$ sudo snap install core; sudo snap refresh core
2021-05-25T18:49:49+09:00 INFO Waiting for automatic snapd restart...
core 16-2.50 from Canonical✓ installed
snap "core" has no updates available
[centos@example ~]$ sudo snap install --classic certbot
certbot 1.15.0 from Certbot Project (certbot-eff✓) installed
手動で証明書を発行する
Certbotの使い方は詳しく説明しているサイトがたくさんあるので省略します。
僕の場合は以下のコマンドで実行します。
[root@example ~]# certbot certonly --manual -d example.tsuchinokometal.com -d *.example.tsuchinokometal.com --preferred-challenges dns -m [あなたのメールアドレス] --agree-tos --no-eff-email --manual-public-ip-logging-ok
Use of --manual-public-ip-logging-ok is deprecated.
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Account registered.
Requesting a certificate for example.tsuchinokometal.com and *.example.tsuchinokometal.com
Performing the following challenges:
dns-01 challenge for example.tsuchinokometal.com
dns-01 challenge for example.tsuchinokometal.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:
_acme-challenge.example.tsuchinokometal.com.
with the following value:
J_pPdq-P-nWuP7w1caChI-fWwM1bu_zHZcx6m0Bqp60
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:
_acme-challenge.example.tsuchinokometal.com.
with the following value:
-feqwo9Ob3zAKQnfDG56GvgRbcaFgwadejin_JNXRxA
(This must be set up in addition to the previous challenges; do not remove,
replace, or undo the previous challenge tasks yet. Note that you might be
asked to create multiple distinct TXT records with the same name. This is
permitted by DNS standards.)
Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.example.tsuchinokometal.com.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
ここまできたらエンターは押さずにそのままで、
指定されたホスト名と値でDNSサーバーにTXTレコードで設定します。
(ドメインを2つ指定しているので2レコード設定が必要です)
設定の仕方はDNSサーバーによって違いますが、
digコマンドで以下のように返って来るようになれば大丈夫です。
% dig -n txt _acme-challenge.example.tsuchinokometal.com +short
"-feqwo9Ob3zAKQnfDG56GvgRbcaFgwadejin_JNXRxA"
"J_pPdq-P-nWuP7w1caChI-fWwM1bu_zHZcx6m0Bqp60"
ではエンターを押して進んでください。
以下のようになれば証明書発行完了です。
Waiting for verification...
Cleaning up challenges
Use of --manual-public-ip-logging-ok is deprecated.
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.tsuchinokometal.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.tsuchinokometal.com/privkey.pem
Your certificate will expire on 2021-08-25. To obtain a new or
tweaked version of this certificate in the future, simply run
certbot again. To non-interactively renew *all* of your
certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
書いてあるとおり、/etc/letsencrypt/live/[あなたのドメイン]/に、
発行された証明書などが保存されていると思います。
もう必要ないのでTXTレコードは削除してしまっていいです。
これだけで無料で証明書が発行できるなんて!
ありがとうLet’s Encrypt!
発行した証明書を利用する
GitLabやRedmineで使ってみます。
例えば以下のようなディレクトリ構成にしたとします。
.
├── gitlab
│ └── docker-compose.yml
├── proxy
│ ├── docker-compose.yml
│ └── ssl.conf
└── redmine
└── docker-compose.yml
まずGitLabとRedmineを起動しておいてください。
以前書いたこちら
が参考になると思います。
今回使ったdocker-compose.ymlをそのまま載せます。
外部ネットワークを作成して全てのコンテナが
同じネットワークに作成されるようにしてください。
gitlab/docker-compose.yml
version: '2.3'
services:
redis:
restart: always
image: redis:5.0.9
container_name: redis
command:
- --loglevel warning
volumes:
- redis-data:/var/lib/redis:Z
postgresql:
restart: always
image: sameersbn/postgresql:12-20200524
container_name: postgresql
volumes:
- postgresql-data:/var/lib/postgresql:Z
environment:
- DB_USER=gitlab
- DB_PASS=password
- DB_NAME=gitlabhq_production
- DB_EXTENSION=pg_trgm,btree_gist
gitlab:
restart: always
image: sameersbn/gitlab:13.12.0
container_name: gitlab
depends_on:
- redis
- postgresql
ports:
- "10080:80"
- "10022:22"
volumes:
- gitlab-data:/home/git/data:Z
- /etc/letsencrypt:/letsencrypt:ro
healthcheck:
test: ["CMD", "/usr/local/sbin/healthcheck"]
interval: 5m
timeout: 10s
retries: 3
start_period: 5m
environment:
- DEBUG=false
- DB_ADAPTER=postgresql
- DB_HOST=postgresql
- DB_PORT=5432
- DB_USER=gitlab
- DB_PASS=password
- DB_NAME=gitlabhq_production
- REDIS_HOST=redis
- REDIS_PORT=6379
- TZ=Asia/Tokyo
- GITLAB_TIMEZONE=Tokyo
- GITLAB_HTTPS=true
- SSL_SELF_SIGNED=false
- GITLAB_HOST=example.tsuchinokometal.com
- GITLAB_PORT=443
- GITLAB_SSH_PORT=10022
- GITLAB_RELATIVE_URL_ROOT=/gitlab
- GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alphanumeric-string
- GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string
- GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alphanumeric-string
- GITLAB_ROOT_PASSWORD=
- GITLAB_ROOT_EMAIL=
- GITLAB_NOTIFY_ON_BROKEN_BUILDS=true
- GITLAB_NOTIFY_PUSHER=false
- GITLAB_EMAIL=notifications@example.com
- GITLAB_EMAIL_REPLY_TO=noreply@example.com
- GITLAB_INCOMING_EMAIL_ADDRESS=reply@example.com
- GITLAB_BACKUP_SCHEDULE=daily
- GITLAB_BACKUP_TIME=01:00
- SMTP_ENABLED=false
- SMTP_DOMAIN=www.example.com
- SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587
- SMTP_USER=mailer@example.com
- SMTP_PASS=password
- SMTP_STARTTLS=true
- SMTP_AUTHENTICATION=login
- IMAP_ENABLED=false
- IMAP_HOST=imap.gmail.com
- IMAP_PORT=993
- IMAP_USER=mailer@example.com
- IMAP_PASS=password
- IMAP_SSL=true
- IMAP_STARTTLS=false
- OAUTH_ENABLED=false
- OAUTH_AUTO_SIGN_IN_WITH_PROVIDER=
- OAUTH_ALLOW_SSO=
- OAUTH_BLOCK_AUTO_CREATED_USERS=true
- OAUTH_AUTO_LINK_LDAP_USER=false
- OAUTH_AUTO_LINK_SAML_USER=false
- OAUTH_EXTERNAL_PROVIDERS=
- OAUTH_CAS3_LABEL=cas3
- OAUTH_CAS3_SERVER=
- OAUTH_CAS3_DISABLE_SSL_VERIFICATION=false
- OAUTH_CAS3_LOGIN_URL=/cas/login
- OAUTH_CAS3_VALIDATE_URL=/cas/p3/serviceValidate
- OAUTH_CAS3_LOGOUT_URL=/cas/logout
- OAUTH_GOOGLE_API_KEY=
- OAUTH_GOOGLE_APP_SECRET=
- OAUTH_GOOGLE_RESTRICT_DOMAIN=
- OAUTH_FACEBOOK_API_KEY=
- OAUTH_FACEBOOK_APP_SECRET=
- OAUTH_TWITTER_API_KEY=
- OAUTH_TWITTER_APP_SECRET=
- OAUTH_GITHUB_API_KEY=
- OAUTH_GITHUB_APP_SECRET=
- OAUTH_GITHUB_URL=
- OAUTH_GITHUB_VERIFY_SSL=
- OAUTH_GITLAB_API_KEY=
- OAUTH_GITLAB_APP_SECRET=
- OAUTH_BITBUCKET_API_KEY=
- OAUTH_BITBUCKET_APP_SECRET=
- OAUTH_BITBUCKET_URL=
- OAUTH_SAML_ASSERTION_CONSUMER_SERVICE_URL=
- OAUTH_SAML_IDP_CERT_FINGERPRINT=
- OAUTH_SAML_IDP_SSO_TARGET_URL=
- OAUTH_SAML_ISSUER=
- OAUTH_SAML_LABEL="Our SAML Provider"
- OAUTH_SAML_NAME_IDENTIFIER_FORMAT=urn:oasis:names:tc:SAML:2.0:nameid-format:transient
- OAUTH_SAML_GROUPS_ATTRIBUTE=
- OAUTH_SAML_EXTERNAL_GROUPS=
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_EMAIL=
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_NAME=
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_USERNAME=
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_FIRST_NAME=
- OAUTH_SAML_ATTRIBUTE_STATEMENTS_LAST_NAME=
- OAUTH_CROWD_SERVER_URL=
- OAUTH_CROWD_APP_NAME=
- OAUTH_CROWD_APP_PASSWORD=
- OAUTH_AUTH0_CLIENT_ID=
- OAUTH_AUTH0_CLIENT_SECRET=
- OAUTH_AUTH0_DOMAIN=
- OAUTH_AUTH0_SCOPE=
- OAUTH_AZURE_API_KEY=
- OAUTH_AZURE_API_SECRET=
- OAUTH_AZURE_TENANT_ID=
- GITLAB_PAGES_ENABLED=true
- GITLAB_PAGES_HTTPS=true
- GITLAB_PAGES_DOMAIN=example.tsuchinokometal.com
- GITLAB_PAGES_PORT=443
- GITLAB_REGISTRY_ENABLED=true
- GITLAB_REGISTRY_HOST=example.tsuchinokometal.com
- GITLAB_REGISTRY_PORT=443
- GITLAB_REGISTRY_API_URL=http://registry:5000
- GITLAB_REGISTRY_KEY_PATH=/letsencrypt/live/example.tsuchinokometal.com/privkey.pem
registry:
restart: always
image: registry:2
container_name: registry
expose:
- "5000"
ports:
- "5000:5000"
volumes:
- registry-data:/registry
- /etc/letsencrypt:/letsencrypt:ro
environment:
- REGISTRY_LOG_LEVEL=info
- REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/registry
- REGISTRY_AUTH_TOKEN_REALM=https://example.tsuchinokometal.com/gitlab/jwt/auth
- REGISTRY_AUTH_TOKEN_SERVICE=container_registry
- REGISTRY_AUTH_TOKEN_ISSUER=gitlab-issuer
- REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/letsencrypt/live/example.tsuchinokometal.com/fullchain.pem
- REGISTRY_STORAGE_DELETE_ENABLED=true
volumes:
redis-data:
postgresql-data:
gitlab-data:
registry-data:
networks:
default:
external:
name: tsuchinoko
GitLabからregistryにアクセスしようとしたら、
production.logで以下のエラーが出ました。
Completed 500 Internal Server Error in 42ms (ActiveRecord: 14.8ms | Elasticsearch: 0.0ms | Allocations: 17154)
Errno::EACCES (Permission denied @ rb_sysopen - /letsencrypt/live/example.tsuchinokometal.com/privkey.pem):
僕はアクセス権を書き換えて対応しましたが、もっといい方法があったら教えて欲しいです。
[root@example ~]# chmod -R 755 /etc/letsencrypt/archive/
[root@example ~]# chmod -R 755 /etc/letsencrypt/live/
GitLabとregistryの連携については こちら が参考になると思います。
redmine/docker-compose.yml
version: '2'
services:
redmine_postgresql:
restart: always
image: sameersbn/postgresql:9.6-4
container_name: redmine_postgresql
environment:
- DB_USER=redmine
- DB_PASS=password
- DB_NAME=redmine_production
volumes:
- redmine_postgresql-data:/var/lib/postgresql
redmine:
restart: always
image: sameersbn/redmine:4.2.1
container_name: redmine
depends_on:
- redmine_postgresql
environment:
- TZ=Asia/Tokyo
- DB_ADAPTER=postgresql
- DB_HOST=redmine_postgresql
- DB_PORT=5432
- DB_USER=redmine
- DB_PASS=password
- DB_NAME=redmine_production
- REDMINE_PORT=10083
- REDMINE_HTTPS=true
- REDMINE_RELATIVE_URL_ROOT=/redmine
- REDMINE_SECRET_TOKEN=
- REDMINE_SUDO_MODE_ENABLED=false
- REDMINE_SUDO_MODE_TIMEOUT=15
- REDMINE_CONCURRENT_UPLOADS=2
- REDMINE_BACKUP_SCHEDULE=
- REDMINE_BACKUP_EXPIRY=
- REDMINE_BACKUP_TIME=
- SMTP_ENABLED=false
- SMTP_METHOD=smtp
- SMTP_DOMAIN=www.example.com
- SMTP_HOST=smtp.gmail.com
- SMTP_PORT=587
- SMTP_USER=mailer@example.com
- SMTP_PASS=password
- SMTP_STARTTLS=true
- SMTP_AUTHENTICATION=:login
- IMAP_ENABLED=false
- IMAP_HOST=imap.gmail.com
- IMAP_PORT=993
- IMAP_USER=mailer@example.com
- IMAP_PASS=password
- IMAP_SSL=true
- IMAP_INTERVAL=30
ports:
- "10083:80"
volumes:
- redmine-data:/home/redmine/data
- redmine-logs:/var/log/redmine
volumes:
redmine_postgresql-data:
redmine-data:
redmine-logs:
networks:
default:
external:
name: tsuchinoko
次にNGINXを起動します。
proxy/docker-compose.yml
version: '3'
services:
proxy:
image: nginx:stable
container_name: proxy
volumes:
- ./ssl.conf:/etc/nginx/conf.d/ssl.conf
- /etc/letsencrypt:/letsencrypt:ro
ports:
- "80:80"
- "443:443"
networks:
default:
external:
name: tsuchinoko
proxy/ssl.conf
server {
root /dev/null;
server_name *.example.tsuchinokometal.com example.tsuchinokometal.com;
charset UTF-8;
access_log /var/log/nginx/example.tsuchinokometal.com.access.log;
error_log /var/log/nginx/example.tsuchinokometal.com.error.log;
# Set up SSL only connections:
listen *:443 ssl http2;
ssl_certificate /letsencrypt/live/example.tsuchinokometal.com/fullchain.pem;
ssl_certificate_key /letsencrypt/live/example.tsuchinokometal.com/privkey.pem;
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4';
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_session_timeout 5m;
client_max_body_size 0;
chunked_transfer_encoding on;
proxy_set_header Host $http_host; # required for docker client's sake
proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 900;
location /gitlab {
proxy_pass http://gitlab;
}
location / {
proxy_pass http://gitlab;
}
location /redmine {
proxy_pass http://redmine;
}
location /v2/ {
proxy_pass http://registry:5000;
}
}
server {
listen *:80;
server_name *.example.tsuchinokometal.com example.tsuchinokometal.com;
server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$http_host:$request_uri;
}
では構築したサーバーの名前解決ができるように
内部DNSサーバーかhostsファイルへ設定を追加してください。
httpsでアクセスできるかご確認ください。
registryと連携できていれば以下のページが表示できます。
ログインしてみると例のエラーが表示されずにログインできますね!
% docker login example.tsuchinokometal.com
Username: root
Password:
Login Succeeded
さらにオンプレKubernetesと連携してGitLab Runnerをインストールしても、
例のエラーが表示されずにインストールできました!
※こちら
を参照してください。
さらにさらに、ワイルドカードでも証明書を作っておいたので、
GitLab Pagesも暗号化してアクセスできます!
※こちら
を参照してください。
なんか集大成みたいな感じになりましたが、
GitLabはまだまだ便利な機能があるのでもっと使い倒して行きたいと思います。