App engine에서 Lets encrypt 사용해서 ssl 적용하기
2017-08-21 14:30 KST
https://code.luasoftware.com/tutorials/google-app-engine/lets-encrypt-on-google-app-engine/ 을 참고 하여 Renewal 시에 storage에 verification file을 업로드하고 서버가 해당 파일을 불러올 수 있게 바꿔보았습니다.
System Requirements
Python 2.6, 2.7, or 3.3+,
root 사용자가 /etc/letsencrypt
, /var/log/letsencrypt
, /var/lib/letsencrypt
에 접근할 수 있어야 합니다.
OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.
즉 윈도우는 지원이 안됩니다.
Install Let’s encrypt
Let’s encrypt ACME Client로 Certbot 을 사용합니다.
user@webserver:~$ wget https://dl.eff.org/certbot-auto
user@webserver:~$ chmod a+x ./certbot-auto
user@webserver:~$ ./certbot-auto --help
따로 플러그인을 사용하지 않을 예정이니 컴파일 버전을 받았습니다.
Install Gcloud
https://cloud.google.com/sdk/docs/quickstarts
각 환경에 맞춰서 설치하시기 바랍니다.
command beta, gsutil을 사용할 것이니 설치해주세요.
gcloud components install beta
gcloud components install gsutil
Create SSL cert
Let’s encrypt에서 따로 App engine에서 인증해주는 도구가 없으니 직접 작성하셔야 합니다.
Setting for App Engine
각 언어에 맞춰서 /.well-known/acme-challenge/[VERIFICATION_FILE_NAME]
로 접근하면 gs://my-bucket/ssl/[VERIFICATION_FILE_NAME]
의 파일 내용을 출력하게 해주세요.
go와 php로 작성한 게 있어서 아래는 예제 소스입니다.
php
app.yaml
runtime : php55
handlers :
- url : /.well-known/acme-challenge/.*
script : acme-challenge.php
env_variables :
GS_BUCKET : [ YOUR_BUCKET_ID ]
acme-challenge.php
<?php
$path = explode ( '/' , $_SERVER [ 'PATH_INFO' ]);
$verifyName = end ( $path );
if ( file_exists ( 'gs://' . getenv ( 'GS_BUCKET' ) . '/ssl/' . $verifyName )) {
$fileContents = file_get_contents ( 'gs://' . getenv ( 'GS_BUCKET' ) . '/ssl/' . $verifyName );
echo $fileContents ;
} else {
http_response_code ( 404 );
die ();
}
go
app.yaml
runtime : go
api_version : go1.8
handlers :
- url : /.*
script : _go_app
main.go
package main
import (
"net/http"
"fmt"
"github.com/gorilla/mux"
"google.golang.org/appengine"
"google.golang.org/appengine/log"
"cloud.google.com/go/storage"
"io/ioutil"
"os"
"context"
)
func init () {
route := mux . NewRouter ()
route . HandleFunc ( "/" , index )
route . HandleFunc ( "/.well-known/acme-challenge/{encrypt}" , acmeChallenge )
http . Handle ( "/" , route )
}
func index ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprint ( w , "Hello World" )
}
func acmeChallenge ( w http . ResponseWriter , r * http . Request ) {
vars := mux . Vars ( r )
encrypt := vars [ "encrypt" ]
var ctx context . Context
ctx = appengine . NewContext ( r )
client , err := storage . NewClient ( ctx )
if err != nil {
log . Errorf ( ctx , "failed to create client: %v" , err )
w . WriteHeader ( 500 )
return
}
defer client . Close ()
bucketName := os . Getenv ( "GS_BUCKET" )
cBucket := client . Bucket ( bucketName )
rc , err := cBucket . Object ( encrypt ) . NewReader ( ctx )
if err != nil {
log . Errorf ( ctx , "readFile: unable to open file from bucket %q, file %q: %v" , bucketName , encrypt , err )
w . WriteHeader ( 404 )
return
}
defer rc . Close ()
slurp , err := ioutil . ReadAll ( rc )
if err != nil {
log . Errorf ( ctx , "readFile: unable to open file from bucket %q, file %q: %v" , bucketName , encrypt , err )
w . WriteHeader ( 404 )
return
}
fmt . Fprint ( w , string ( slurp ))
}
Config Domain for App engine
SSL을 사용할 맞춤 도메인을 추가하여 주세요.
Create shell script HTTP verification file upload to Cloud Storage
certbot의 --manual-auth-hook
을 이용하여 verification시 Cloud Storage에 Upload되도록 합시다.
auth-hook.sh
#!/bin/bash
# for debugging purpose only
set -x
echo $CERTBOT_VALIDATION | gsutil cp -a project-private - gs://my-bucket/ssl/$CERTBOT_TOKEN
set +x
Create shell script SSL Renewal hook
renew 시에 app engine에 SSL을 업로드하고 해당 도메인에 SSL을 매핑해줘야 합니다.
renew-hook.sh
#!/bin/bash
set -x
PROJECT_NAME = your-project-id
#gcloud config set project $PROJECT_NAME
NOW = $( date +"%y/%m/%d" )
openssl rsa -in $RENEWED_LINEAGE /privkey.pem -out $RENEWED_LINEAGE /privkeyrsa.pem
CERTID = $( gcloud beta app ssl-certificates create \
--display-name $PROJECT_NAME -$NOW \
--certificate $RENEWED_LINEAGE /fullchain.pem \
--private-key $RENEWED_LINEAGE /privkeyrsa.pem \
--project $PROJECT_NAME --format = 'value(id)' )
for domain in $RENEWED_DOMAINS ; do
gcloud beta app domain-mappings update $domain --certificate-id $CERTID --project $PROJECT_NAME
done
set +x
Run Script
certbot-auto certonly --manual --manual-public-ip-logging-ok \
--preferred-challenges http --manual-auth-hook /[FULLPATH]/auth-hook.sh \
--renew-hook /[FULLPATH]/renew-hook.sh -d your-domain.com -d www.your-domain.com
Results
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/asdf.jhbae.in/fullchain.pem. Your cert will
expire on 2017-11-19. 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
Test Renewal
certbot-auto renew --dry-run
Register Crontab
sudo crontab -e
# m h dom mon dow command
7 3 * * * /FULL/PATH/TO/certbot-auto renew --quiet
정상적으로 등록이 되었습니다.