Skip to content

Commit 007ea82

Browse files
authored
Implemented TLS (#25)
Close #12
1 parent af7b7a5 commit 007ea82

File tree

6 files changed

+97
-15
lines changed

6 files changed

+97
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [0.0.19] - 2022-05-14
1010

11+
### Added
12+
13+
- Implemented TLS mode
14+
1115
### Changed
1216

1317
- Introduced `--dir` option under `git` backend - now current working directory can be changed dynamically

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,21 @@ Git as Terraform backend? Seriously? I know, might sound like a stupid idea at f
1313
- [From Sources](#from-sources)
1414
- [Usage](#usage)
1515
- [As wrapper](#as-wrapper)
16-
- [with Hashicorp Configuration Language (HCL)](#with-hashicorp-configuration-language-(hcl))
16+
- [with Hashicorp Configuration Language (HCL)](#with-hashicorp-configuration-language-hcl)
1717
- [as Terraform HTTP backend](#as-terraform-http-backend)
1818
- [As Github Action](#as-github-action)
19+
- [Setup action](#setup-action)
20+
- [Inputs](#inputs)
21+
- [`version`](#version)
22+
- [Outputs](#outputs)
23+
- [`version`](#version-1)
24+
- [Example usage](#example-usage)
1925
- [Wrappers CLI](#wrappers-cli)
2026
- [Configuration](#configuration)
2127
- [Git Credentials](#git-credentials)
2228
- [State Encryption](#state-encryption)
2329
- [Running backend remotely](#running-backend-remotely)
30+
- [TLS](#tls)
2431
- [Basic HTTP Authentication](#basic-http-authentication)
2532
- [Why not native Terraform Backend](#why-not-native-terraform-backend)
2633
- [Why storing state in Git](#why-storing-state-in-git)
@@ -234,6 +241,10 @@ Make sure you do not open the port in your firewall for remote connections. By d
234241

235242
You may get creative and use something like K8s Network Policies like `calico`, or wrap backend traffic into API Gateway or ServiceMesh like Istio to add external layer of encryption and authentication, and then at your discretion you may run it with `--address=:6061` argument so the backend will bind to `0.0.0.0` and become remotely accessible.
236243

244+
### TLS
245+
246+
You can set `TF_BACKEND_GIT_HTTPS_CERT` and `TF_BACKEND_GIT_HTTPS_KEY` pointing to your cert and a key files. This will make HTTP backend to start in TLS mode. If you are using self-signed certificate - you can also set `TF_BACKEND_GIT_HTTPS_SKIP_VERIFICATION=true` and that will enable `skip_cert_verification` in terraform config.
247+
237248
### Basic HTTP Authentication
238249

239250
You can use `TF_BACKEND_GIT_HTTP_USERNAME` and `TF_BACKEND_GIT_HTTP_PASSWORD` environment variables to add an extra layer of protection. In `wrapper` mode, same environment variables will be used to render `*.auto.tf` config for Terraform, but if you are using backend in standalone mode - you will have to tell these credentials to the Terraform explicitly:

cmd/git_backend.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ var gitBackendCmd = &cobra.Command{
3434
t, err := template.New(gitHTTPBackendConfigPath).Parse(`
3535
terraform {
3636
backend "http" {
37-
address = "http://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
38-
lock_address = "http://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
39-
unlock_address = "http://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
37+
address = "{{ .protocol }}://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
38+
lock_address = "{{ .protocol }}://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
39+
unlock_address = "{{ .protocol }}://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
40+
skip_cert_verification = {{ .skipHttpsVerification }}
4041
username = "{{ .username }}"
4142
password = "{{ .password }}"
4243
}
@@ -46,14 +47,28 @@ terraform {
4647
log.Fatal(err)
4748
}
4849

50+
_, okHttpCert := os.LookupEnv("TF_BACKEND_GIT_HTTPS_CERT")
51+
_, okHttpKey := os.LookupEnv("TF_BACKEND_GIT_HTTPS_KEY")
52+
protocol := "http"
53+
if okHttpCert && okHttpKey {
54+
protocol = "https"
55+
}
56+
57+
skipHttpsVerification, okSkipHttpsVerification := os.LookupEnv("TF_BACKEND_GIT_HTTPS_SKIP_VERIFICATION")
58+
if !okSkipHttpsVerification {
59+
skipHttpsVerification = "false"
60+
}
61+
4962
username, _ := os.LookupEnv("TF_BACKEND_GIT_HTTP_USERNAME")
5063
password, _ := os.LookupEnv("TF_BACKEND_GIT_HTTP_PASSWORD")
5164

5265
addr := strings.Split(viper.GetString("address"), ":")
5366
p := map[string]string{
54-
"port": addr[len(addr)-1],
55-
"username": username,
56-
"password": password,
67+
"port": addr[len(addr)-1],
68+
"protocol": protocol,
69+
"skipHttpsVerification": skipHttpsVerification,
70+
"username": username,
71+
"password": password,
5772
}
5873

5974
for _, flag := range []string{"repository", "ref", "state"} {

server/server.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ func Start() {
3434

3535
address := viper.GetString("address")
3636
log.Println("listen on", address)
37-
log.Fatal(http.ListenAndServe(address, mux))
37+
38+
httpCert, okHttpCert := os.LookupEnv("TF_BACKEND_GIT_HTTPS_CERT")
39+
httpKey, okHttpKey := os.LookupEnv("TF_BACKEND_GIT_HTTPS_KEY")
40+
if okHttpCert && okHttpKey {
41+
log.Fatal(http.ListenAndServeTLS(address, httpCert, httpKey, mux))
42+
} else {
43+
log.Fatal(http.ListenAndServe(address, mux))
44+
}
3845
}
3946

4047
// basicAuth checking for user authentication
@@ -163,7 +170,7 @@ func handleFunc(response http.ResponseWriter, request *http.Request) {
163170

164171
response.Header().Set("Content-Type", "application/json")
165172
response.WriteHeader(http.StatusOK)
166-
response.Write(state)
173+
_, _ = response.Write(state)
167174
case http.MethodPost:
168175
log.Printf("Saving state to %s", metadata.Params.String())
169176

@@ -221,25 +228,24 @@ func (handler *handler) clientError(err error) {
221228
// If error was unknown, just use defaultCode and defaultResponse error message.
222229
func (handler *handler) responseError(defaultCode int, defaultResponse string, actualErr error) {
223230
log.Printf("%s", actualErr)
224-
switch actualErr.(type) {
231+
switch actualErr := actualErr.(type) {
225232
case *types.ErrLocked:
226233
handler.Response.WriteHeader(http.StatusConflict)
227-
handler.Response.Write(actualErr.(*types.ErrLocked).Lock)
234+
_, _ = handler.Response.Write(actualErr.Lock)
228235
default:
229236
switch actualErr {
230237
case types.ErrLockMissing:
231238
handler.Response.WriteHeader(http.StatusPreconditionRequired)
232-
handler.Response.Write([]byte("428 - Locking Required"))
239+
_, _ = handler.Response.Write([]byte("428 - Locking Required"))
233240
case types.ErrStateDidNotExisted:
234241
handler.Response.WriteHeader(http.StatusNoContent)
235242
case types.ErrUnauthorized:
236243
handler.Response.Header().Set("WWW-Authenticate", `Basic realm=terraform-backend-git`)
237244
handler.Response.WriteHeader(http.StatusUnauthorized)
238-
handler.Response.Write([]byte("401 - Unauthorized"))
245+
_, _ = handler.Response.Write([]byte("401 - Unauthorized"))
239246
default:
240247
handler.Response.WriteHeader(defaultCode)
241-
handler.Response.Write([]byte(defaultResponse))
248+
_, _ = handler.Response.Write([]byte(defaultResponse))
242249
}
243-
244250
}
245251
}

test/tf/localhost.crt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIC8DCCAdigAwIBAgIUMf5T8W7Y8GgzORoeySpVL45UnAswDQYJKoZIhvcNAQEL
3+
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDUxNTA0NDE0M1oXDTIyMDYx
4+
NDA0NDE0M1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
5+
AAOCAQ8AMIIBCgKCAQEA1bISvpDhmZ35ekHx2TZaXwTlChwmuk2fUe21AIB8iNiq
6+
Le27el6xRG2eyEwV5MioXLAmWMNyW2AK+R+rnAsXBcGSY52ud+Bh9Hq/3HwH0ulD
7+
4iVZkvDVQyH2RA5o+09gtoq71vQvBHLqQySml5K9KFd4SWRPduwx8sm+15PJtnOO
8+
ap0qmRjnd8nGe5fOZDGQakTOZ/+ym3T5eziIIMEi43vKnPcFaQY3AhO8rNDI8x1p
9+
JVt2zss1ieaHWLUxd0jx0lmkPpc0m2iR9qJGoIJpxbONZaYIU1ACPBnPkK+/w7Hs
10+
q+6dcaWbe163buCbu7toCIcpYOh/PtdlOZdZm3fwPwIDAQABozowODAUBgNVHREE
11+
DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB
12+
MA0GCSqGSIb3DQEBCwUAA4IBAQBjWnRYlqm3UeNZjwWaH/VGiIctW2C2gRLldWhb
13+
CBxcJ6dga2Ys8vcOZ1tTFtBVUZyT0NgCzZ3eJD5n0E9s9UR8QbNzJxhBWJgvC+qV
14+
vJV/K7AN1eXnr9DU+V/IoEOBJdF12DljeS6vWlxEdfmDqeO6A1q66mz0wnYcC63I
15+
/3O651SJOCI11/oD5lMHX5la2wBEytn0vYRehOSA22EmGo4aSNKLP/ac38ptCLhk
16+
ikkIJXEu0B0O+vvrwEeSy/encL6p+C2bybTdrbVs4b5+BwqJxfDEyDSq03loI4k4
17+
oevFJoyBSX3lhK8JnyjKdY13srZIm2PESxvTZKJiNsN894v3
18+
-----END CERTIFICATE-----

test/tf/localhost.key

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVshK+kOGZnfl6
3+
QfHZNlpfBOUKHCa6TZ9R7bUAgHyI2Kot7bt6XrFEbZ7ITBXkyKhcsCZYw3JbYAr5
4+
H6ucCxcFwZJjna534GH0er/cfAfS6UPiJVmS8NVDIfZEDmj7T2C2irvW9C8EcupD
5+
JKaXkr0oV3hJZE927DHyyb7Xk8m2c45qnSqZGOd3ycZ7l85kMZBqRM5n/7KbdPl7
6+
OIggwSLje8qc9wVpBjcCE7ys0MjzHWklW3bOyzWJ5odYtTF3SPHSWaQ+lzSbaJH2
7+
okaggmnFs41lpghTUAI8Gc+Qr7/Dseyr7p1xpZt7Xrdu4Ju7u2gIhylg6H8+12U5
8+
l1mbd/A/AgMBAAECgf8gjgBNB8VWobpf5avya1VZJGXAJInB2BFtackpSmmub1N9
9+
q7nj6okW45xM50pukCMV+7/bxeqmAPuq+CgsnYPkXJjwlBUALi6+D/UqobqPZvnw
10+
ecArooTatVHPDGLx5iXVqUz0cj63bspBcFPww2oNu0WX6LAybckuM43fSaRxJuJC
11+
5wlUvCd8wiLn9bEJNPDypfK2ktrWIoPj13GCNgFkolcsaypN/KPEe5H08AFudf4P
12+
qyB5VD6U1NszUcFqHpxELXxyrEyA5QF+KEQQETZr5hXkJEUoRHT5rllZkq5emVqc
13+
gv9o/5Fwwcvqxk/i1IEQev4LlmTSMb5Z+not87kCgYEA707t5aL1b4mLWstT1XSH
14+
ytveXkdqGZqhzYOsfQhAjYIgy9Gx/h1GNEweKjTfU9k05CvhC1+0P67CUqbj8YLZ
15+
QkO4tF8J6bdxR/uU/ujutZy6BpxW0Ph2TNr3MWZ6FHTos0CzNFu6G0Az9Bc9/2h5
16+
zvVx6RMycy8tAvZbqqEAhaUCgYEA5JnOIpu90l5oRoGvo6GPqkjj1sfTgp5uZuGD
17+
SnAav10oOyIvHM4IlLu4R2DrI+jlvJ/OhMFuJGR9OwyP5c3fHJ0l2BMFG8hwqeZJ
18+
8PB3o9LpEgLlIbM4I2A6vshCl/4tgD4qSMctSNaSO6Knq644ePJdFXfajKlX6Iay
19+
9iWY4RMCgYEAyshJUqOp8p/M1F0jZudeAgoZ/i3pvFSJ80o2qaSKft7bx5qjhz9r
20+
M/mkPgObksOlzAtaoXaxmJ0P0VXWJdrJGxujskQudDub5HFNKkxbqs1p3RwxfNZt
21+
+GY7vUKnBBqk7PBQanen1lurKpVfVcREI5lcszIvN+er7qyvtIDFnnkCgYEA2/wy
22+
xVUwb3AQUsFcH1BLK2hncPntTcZeobyklo6Y/syL+ZPk2Ihg85hONspKnczyv/jd
23+
SR3He1gEtz1YgKID8co8b9ml0d2qpaUKRMVzrIA7b+y/SRXpkQl6nruichfU+5NX
24+
J6AcsPpj0OWvCuRmTeWVtCIZe8E+6nItZ/g4TWcCgYEA5ioiA6/uVlOwYF/Pckxc
25+
9jJM6Pr+/taQkkdmrIo+kn2sBqq0v01pns9gMm243fWibp9rCanh6g5fNdgWErqB
26+
vOTzHENSMXZsdki+yWrlCswT9cldNcd1dYSMHVHulWQOSB5zF7YBi0WhQMa3coox
27+
6k4TesK9sHln8317eDYQlV0=
28+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)