Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.0.19] - 2022-05-14

### Added

- Implemented TLS mode

### Changed

- Introduced `--dir` option under `git` backend - now current working directory can be changed dynamically
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ Git as Terraform backend? Seriously? I know, might sound like a stupid idea at f
- [From Sources](#from-sources)
- [Usage](#usage)
- [As wrapper](#as-wrapper)
- [with Hashicorp Configuration Language (HCL)](#with-hashicorp-configuration-language-(hcl))
- [with Hashicorp Configuration Language (HCL)](#with-hashicorp-configuration-language-hcl)
- [as Terraform HTTP backend](#as-terraform-http-backend)
- [As Github Action](#as-github-action)
- [Setup action](#setup-action)
- [Inputs](#inputs)
- [`version`](#version)
- [Outputs](#outputs)
- [`version`](#version-1)
- [Example usage](#example-usage)
- [Wrappers CLI](#wrappers-cli)
- [Configuration](#configuration)
- [Git Credentials](#git-credentials)
- [State Encryption](#state-encryption)
- [Running backend remotely](#running-backend-remotely)
- [TLS](#tls)
- [Basic HTTP Authentication](#basic-http-authentication)
- [Why not native Terraform Backend](#why-not-native-terraform-backend)
- [Why storing state in Git](#why-storing-state-in-git)
Expand Down Expand Up @@ -234,6 +241,10 @@ Make sure you do not open the port in your firewall for remote connections. By d

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.

### TLS

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.

### Basic HTTP Authentication

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:
Expand Down
27 changes: 21 additions & 6 deletions cmd/git_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ var gitBackendCmd = &cobra.Command{
t, err := template.New(gitHTTPBackendConfigPath).Parse(`
terraform {
backend "http" {
address = "http://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
lock_address = "http://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
unlock_address = "http://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
address = "{{ .protocol }}://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
lock_address = "{{ .protocol }}://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
unlock_address = "{{ .protocol }}://localhost:{{ .port }}/?type=git&repository={{ .repository }}&ref={{ .ref }}&state={{ .state }}"
skip_cert_verification = {{ .skipHttpsVerification }}
username = "{{ .username }}"
password = "{{ .password }}"
}
Expand All @@ -46,14 +47,28 @@ terraform {
log.Fatal(err)
}

_, okHttpCert := os.LookupEnv("TF_BACKEND_GIT_HTTPS_CERT")
_, okHttpKey := os.LookupEnv("TF_BACKEND_GIT_HTTPS_KEY")
protocol := "http"
if okHttpCert && okHttpKey {
protocol = "https"
}

skipHttpsVerification, okSkipHttpsVerification := os.LookupEnv("TF_BACKEND_GIT_HTTPS_SKIP_VERIFICATION")
if !okSkipHttpsVerification {
skipHttpsVerification = "false"
}

username, _ := os.LookupEnv("TF_BACKEND_GIT_HTTP_USERNAME")
password, _ := os.LookupEnv("TF_BACKEND_GIT_HTTP_PASSWORD")

addr := strings.Split(viper.GetString("address"), ":")
p := map[string]string{
"port": addr[len(addr)-1],
"username": username,
"password": password,
"port": addr[len(addr)-1],
"protocol": protocol,
"skipHttpsVerification": skipHttpsVerification,
"username": username,
"password": password,
}

for _, flag := range []string{"repository", "ref", "state"} {
Expand Down
22 changes: 14 additions & 8 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ func Start() {

address := viper.GetString("address")
log.Println("listen on", address)
log.Fatal(http.ListenAndServe(address, mux))

httpCert, okHttpCert := os.LookupEnv("TF_BACKEND_GIT_HTTPS_CERT")
httpKey, okHttpKey := os.LookupEnv("TF_BACKEND_GIT_HTTPS_KEY")
if okHttpCert && okHttpKey {
log.Fatal(http.ListenAndServeTLS(address, httpCert, httpKey, mux))
} else {
log.Fatal(http.ListenAndServe(address, mux))
}
}

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

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

Expand Down Expand Up @@ -221,25 +228,24 @@ func (handler *handler) clientError(err error) {
// If error was unknown, just use defaultCode and defaultResponse error message.
func (handler *handler) responseError(defaultCode int, defaultResponse string, actualErr error) {
log.Printf("%s", actualErr)
switch actualErr.(type) {
switch actualErr := actualErr.(type) {
case *types.ErrLocked:
handler.Response.WriteHeader(http.StatusConflict)
handler.Response.Write(actualErr.(*types.ErrLocked).Lock)
_, _ = handler.Response.Write(actualErr.Lock)
default:
switch actualErr {
case types.ErrLockMissing:
handler.Response.WriteHeader(http.StatusPreconditionRequired)
handler.Response.Write([]byte("428 - Locking Required"))
_, _ = handler.Response.Write([]byte("428 - Locking Required"))
case types.ErrStateDidNotExisted:
handler.Response.WriteHeader(http.StatusNoContent)
case types.ErrUnauthorized:
handler.Response.Header().Set("WWW-Authenticate", `Basic realm=terraform-backend-git`)
handler.Response.WriteHeader(http.StatusUnauthorized)
handler.Response.Write([]byte("401 - Unauthorized"))
_, _ = handler.Response.Write([]byte("401 - Unauthorized"))
default:
handler.Response.WriteHeader(defaultCode)
handler.Response.Write([]byte(defaultResponse))
_, _ = handler.Response.Write([]byte(defaultResponse))
}

}
}
18 changes: 18 additions & 0 deletions test/tf/localhost.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC8DCCAdigAwIBAgIUMf5T8W7Y8GgzORoeySpVL45UnAswDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDUxNTA0NDE0M1oXDTIyMDYx
NDA0NDE0M1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA1bISvpDhmZ35ekHx2TZaXwTlChwmuk2fUe21AIB8iNiq
Le27el6xRG2eyEwV5MioXLAmWMNyW2AK+R+rnAsXBcGSY52ud+Bh9Hq/3HwH0ulD
4iVZkvDVQyH2RA5o+09gtoq71vQvBHLqQySml5K9KFd4SWRPduwx8sm+15PJtnOO
ap0qmRjnd8nGe5fOZDGQakTOZ/+ym3T5eziIIMEi43vKnPcFaQY3AhO8rNDI8x1p
JVt2zss1ieaHWLUxd0jx0lmkPpc0m2iR9qJGoIJpxbONZaYIU1ACPBnPkK+/w7Hs
q+6dcaWbe163buCbu7toCIcpYOh/PtdlOZdZm3fwPwIDAQABozowODAUBgNVHREE
DTALgglsb2NhbGhvc3QwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMB
MA0GCSqGSIb3DQEBCwUAA4IBAQBjWnRYlqm3UeNZjwWaH/VGiIctW2C2gRLldWhb
CBxcJ6dga2Ys8vcOZ1tTFtBVUZyT0NgCzZ3eJD5n0E9s9UR8QbNzJxhBWJgvC+qV
vJV/K7AN1eXnr9DU+V/IoEOBJdF12DljeS6vWlxEdfmDqeO6A1q66mz0wnYcC63I
/3O651SJOCI11/oD5lMHX5la2wBEytn0vYRehOSA22EmGo4aSNKLP/ac38ptCLhk
ikkIJXEu0B0O+vvrwEeSy/encL6p+C2bybTdrbVs4b5+BwqJxfDEyDSq03loI4k4
oevFJoyBSX3lhK8JnyjKdY13srZIm2PESxvTZKJiNsN894v3
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions test/tf/localhost.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVshK+kOGZnfl6
QfHZNlpfBOUKHCa6TZ9R7bUAgHyI2Kot7bt6XrFEbZ7ITBXkyKhcsCZYw3JbYAr5
H6ucCxcFwZJjna534GH0er/cfAfS6UPiJVmS8NVDIfZEDmj7T2C2irvW9C8EcupD
JKaXkr0oV3hJZE927DHyyb7Xk8m2c45qnSqZGOd3ycZ7l85kMZBqRM5n/7KbdPl7
OIggwSLje8qc9wVpBjcCE7ys0MjzHWklW3bOyzWJ5odYtTF3SPHSWaQ+lzSbaJH2
okaggmnFs41lpghTUAI8Gc+Qr7/Dseyr7p1xpZt7Xrdu4Ju7u2gIhylg6H8+12U5
l1mbd/A/AgMBAAECgf8gjgBNB8VWobpf5avya1VZJGXAJInB2BFtackpSmmub1N9
q7nj6okW45xM50pukCMV+7/bxeqmAPuq+CgsnYPkXJjwlBUALi6+D/UqobqPZvnw
ecArooTatVHPDGLx5iXVqUz0cj63bspBcFPww2oNu0WX6LAybckuM43fSaRxJuJC
5wlUvCd8wiLn9bEJNPDypfK2ktrWIoPj13GCNgFkolcsaypN/KPEe5H08AFudf4P
qyB5VD6U1NszUcFqHpxELXxyrEyA5QF+KEQQETZr5hXkJEUoRHT5rllZkq5emVqc
gv9o/5Fwwcvqxk/i1IEQev4LlmTSMb5Z+not87kCgYEA707t5aL1b4mLWstT1XSH
ytveXkdqGZqhzYOsfQhAjYIgy9Gx/h1GNEweKjTfU9k05CvhC1+0P67CUqbj8YLZ
QkO4tF8J6bdxR/uU/ujutZy6BpxW0Ph2TNr3MWZ6FHTos0CzNFu6G0Az9Bc9/2h5
zvVx6RMycy8tAvZbqqEAhaUCgYEA5JnOIpu90l5oRoGvo6GPqkjj1sfTgp5uZuGD
SnAav10oOyIvHM4IlLu4R2DrI+jlvJ/OhMFuJGR9OwyP5c3fHJ0l2BMFG8hwqeZJ
8PB3o9LpEgLlIbM4I2A6vshCl/4tgD4qSMctSNaSO6Knq644ePJdFXfajKlX6Iay
9iWY4RMCgYEAyshJUqOp8p/M1F0jZudeAgoZ/i3pvFSJ80o2qaSKft7bx5qjhz9r
M/mkPgObksOlzAtaoXaxmJ0P0VXWJdrJGxujskQudDub5HFNKkxbqs1p3RwxfNZt
+GY7vUKnBBqk7PBQanen1lurKpVfVcREI5lcszIvN+er7qyvtIDFnnkCgYEA2/wy
xVUwb3AQUsFcH1BLK2hncPntTcZeobyklo6Y/syL+ZPk2Ihg85hONspKnczyv/jd
SR3He1gEtz1YgKID8co8b9ml0d2qpaUKRMVzrIA7b+y/SRXpkQl6nruichfU+5NX
J6AcsPpj0OWvCuRmTeWVtCIZe8E+6nItZ/g4TWcCgYEA5ioiA6/uVlOwYF/Pckxc
9jJM6Pr+/taQkkdmrIo+kn2sBqq0v01pns9gMm243fWibp9rCanh6g5fNdgWErqB
vOTzHENSMXZsdki+yWrlCswT9cldNcd1dYSMHVHulWQOSB5zF7YBi0WhQMa3coox
6k4TesK9sHln8317eDYQlV0=
-----END PRIVATE KEY-----