Skip to content

Commit 82c19d1

Browse files
committed
Add mTLS support for keycloak client
1 parent b35eb9f commit 82c19d1

File tree

5 files changed

+48
-20
lines changed

5 files changed

+48
-20
lines changed

docs/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,7 @@ The following arguments are supported:
8787
- `client_timeout` - (Optional) Sets the timeout of the client when addressing Keycloak, in seconds. Defaults to the environment variable `KEYCLOAK_CLIENT_TIMEOUT`, or `15` if the environment variable is not specified.
8888
- `tls_insecure_skip_verify` - (Optional) Allows ignoring insecure certificates when set to `true`. Defaults to `false`. Disabling this security check is dangerous and should only be done in local or test environments.
8989
- `root_ca_certificate` - (Optional) Allows x509 calls using an unknown CA certificate (for development purposes)
90+
- `tls_client_certificate` - (Optional) The TLS client certificate in PEM format when the keycloak server is configured with TLS mutual authentication.
91+
- `tls_client_private_key` - (Optional) The TLS client pkcs1 private key in PEM format when the keycloak server is configured with TLS mutual authentication.
9092
- `base_path` - (Optional) The base path used for accessing the Keycloak REST API. Defaults to the environment variable `KEYCLOAK_BASE_PATH`, or an empty string if the environment variable is not specified. Note that users of the legacy distribution of Keycloak will need to set this attribute to `/auth`.
9193
- `additional_headers` - (Optional) A map of custom HTTP headers to add to each request to the Keycloak API.

keycloak/keycloak_client.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"crypto/x509"
88
"encoding/json"
99
"fmt"
10-
"github.com/hashicorp/terraform-plugin-log/tflog"
1110
"io/ioutil"
1211
"net/http"
1312
"net/http/cookiejar"
@@ -17,6 +16,8 @@ import (
1716
"strings"
1817
"time"
1918

19+
"github.com/hashicorp/terraform-plugin-log/tflog"
20+
2021
"github.com/hashicorp/go-version"
2122

2223
"golang.org/x/net/publicsuffix"
@@ -60,7 +61,7 @@ var redHatSSO7VersionMap = map[int]string{
6061
4: "9.0.17",
6162
}
6263

63-
func NewKeycloakClient(ctx context.Context, url, basePath, clientId, clientSecret, realm, username, password string, initialLogin bool, clientTimeout int, caCert string, tlsInsecureSkipVerify bool, userAgent string, redHatSSO bool, additionalHeaders map[string]string) (*KeycloakClient, error) {
64+
func NewKeycloakClient(ctx context.Context, url, basePath, clientId, clientSecret, realm, username, password string, initialLogin bool, clientTimeout int, caCert string, tlsClientCert string, tlsClientPrivateKey string, tlsInsecureSkipVerify bool, userAgent string, redHatSSO bool, additionalHeaders map[string]string) (*KeycloakClient, error) {
6465
clientCredentials := &ClientCredentials{
6566
ClientId: clientId,
6667
ClientSecret: clientSecret,
@@ -79,7 +80,7 @@ func NewKeycloakClient(ctx context.Context, url, basePath, clientId, clientSecre
7980
}
8081
}
8182

82-
httpClient, err := newHttpClient(tlsInsecureSkipVerify, clientTimeout, caCert)
83+
httpClient, err := newHttpClient(tlsInsecureSkipVerify, clientTimeout, caCert, tlsClientCert, tlsClientPrivateKey)
8384
if err != nil {
8485
return nil, fmt.Errorf("failed to create http client: %v", err)
8586
}
@@ -170,7 +171,6 @@ func (keycloakClient *KeycloakClient) login(ctx context.Context) error {
170171
serverVersion = strings.ReplaceAll(info.SystemInfo.ServerVersion, ".GA", "")
171172
} else {
172173
regex, err := regexp.Compile(`\.redhat-\w+`)
173-
174174
if err != nil {
175175
fmt.Println("Error compiling regex:", err)
176176
return err
@@ -499,7 +499,7 @@ func (keycloakClient *KeycloakClient) marshal(body interface{}) ([]byte, error)
499499
return json.Marshal(body)
500500
}
501501

502-
func newHttpClient(tlsInsecureSkipVerify bool, clientTimeout int, caCert string) (*http.Client, error) {
502+
func newHttpClient(tlsInsecureSkipVerify bool, clientTimeout int, caCert string, tlsClientCert string, tlsClientPrivateKey string) (*http.Client, error) {
503503
cookieJar, err := cookiejar.New(&cookiejar.Options{
504504
PublicSuffixList: publicsuffix.List,
505505
})
@@ -518,6 +518,14 @@ func newHttpClient(tlsInsecureSkipVerify bool, clientTimeout int, caCert string)
518518
transport.TLSClientConfig.RootCAs = caCertPool
519519
}
520520

521+
if tlsClientCert != "" && tlsClientPrivateKey != "" {
522+
clientKeyPairCert, err := tls.X509KeyPair([]byte(tlsClientCert), []byte(tlsClientPrivateKey))
523+
if err != nil {
524+
return nil, err
525+
}
526+
transport.TLSClientConfig.Certificates = []tls.Certificate{clientKeyPairCert}
527+
}
528+
521529
retryClient := retryablehttp.NewClient()
522530
retryClient.RetryMax = 1
523531
retryClient.RetryWaitMin = time.Second * 1

keycloak/keycloak_client_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package keycloak
22

33
import (
44
"context"
5-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
65
"os"
76
"strconv"
87
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
910
)
1011

1112
var requiredEnvironmentVariables = []string{
@@ -48,7 +49,7 @@ func TestAccKeycloakApiClientRefresh(t *testing.T) {
4849
t.Fatal("KEYCLOAK_CLIENT_TIMEOUT must be an integer")
4950
}
5051

51-
keycloakClient, err := NewKeycloakClient(ctx, os.Getenv("KEYCLOAK_URL"), "", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), os.Getenv("KEYCLOAK_USER"), os.Getenv("KEYCLOAK_PASSWORD"), true, clientTimeout, "", false, "", false, map[string]string{
52+
keycloakClient, err := NewKeycloakClient(ctx, os.Getenv("KEYCLOAK_URL"), "", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), os.Getenv("KEYCLOAK_USER"), os.Getenv("KEYCLOAK_PASSWORD"), true, clientTimeout, "", "", "", false, "", false, map[string]string{
5253
"foo": "bar",
5354
})
5455
if err != nil {

provider/provider.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,18 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
174174
Description: "Allows ignoring insecure certificates when set to true. Defaults to false. Disabling security check is dangerous and should be avoided.",
175175
Default: false,
176176
},
177+
"tls_client_certificate": {
178+
Optional: true,
179+
Type: schema.TypeString,
180+
Description: "TLS client certificate as PEM string for mutual authentication",
181+
Default: "",
182+
},
183+
"tls_client_private_key": {
184+
Optional: true,
185+
Type: schema.TypeString,
186+
Description: "TLS client private key as PEM string for mutual authentication",
187+
Default: "",
188+
},
177189
"red_hat_sso": {
178190
Optional: true,
179191
Type: schema.TypeBool,
@@ -210,6 +222,8 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
210222
initialLogin := data.Get("initial_login").(bool)
211223
clientTimeout := data.Get("client_timeout").(int)
212224
tlsInsecureSkipVerify := data.Get("tls_insecure_skip_verify").(bool)
225+
tlsClientCertificate := data.Get("tls_client_certificate").(string)
226+
tlsClientPrivateKey := data.Get("tls_client_private_key").(string)
213227
rootCaCertificate := data.Get("root_ca_certificate").(string)
214228
redHatSSO := data.Get("red_hat_sso").(bool)
215229
additionalHeaders := make(map[string]string)
@@ -221,7 +235,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
221235

222236
userAgent := fmt.Sprintf("HashiCorp Terraform/%s (+https://www.terraform.io) Terraform Plugin SDK/%s", provider.TerraformVersion, meta.SDKVersionString())
223237

224-
keycloakClient, err := keycloak.NewKeycloakClient(ctx, url, basePath, clientId, clientSecret, realm, username, password, initialLogin, clientTimeout, rootCaCertificate, tlsInsecureSkipVerify, userAgent, redHatSSO, additionalHeaders)
238+
keycloakClient, err := keycloak.NewKeycloakClient(ctx, url, basePath, clientId, clientSecret, realm, username, password, initialLogin, clientTimeout, rootCaCertificate, tlsClientCertificate, tlsClientPrivateKey, tlsInsecureSkipVerify, userAgent, redHatSSO, additionalHeaders)
225239
if err != nil {
226240
diags = append(diags, diag.Diagnostic{
227241
Severity: diag.Error,

provider/provider_test.go

+15-12
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
8-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9-
"github.com/hashicorp/terraform-plugin-sdk/v2/meta"
10-
"github.com/keycloak/terraform-provider-keycloak/keycloak"
117
"log"
128
"os"
139
"testing"
1410
"time"
11+
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
14+
"github.com/hashicorp/terraform-plugin-sdk/v2/meta"
15+
"github.com/keycloak/terraform-provider-keycloak/keycloak"
1516
)
1617

17-
var testAccProviderFactories map[string]func() (*schema.Provider, error)
18-
var testAccProvider *schema.Provider
19-
var keycloakClient *keycloak.KeycloakClient
20-
var testAccRealm *keycloak.Realm
21-
var testAccRealmTwo *keycloak.Realm
22-
var testAccRealmUserFederation *keycloak.Realm
23-
var testCtx context.Context
18+
var (
19+
testAccProviderFactories map[string]func() (*schema.Provider, error)
20+
testAccProvider *schema.Provider
21+
keycloakClient *keycloak.KeycloakClient
22+
testAccRealm *keycloak.Realm
23+
testAccRealmTwo *keycloak.Realm
24+
testAccRealmUserFederation *keycloak.Realm
25+
testCtx context.Context
26+
)
2427

2528
var requiredEnvironmentVariables = []string{
2629
"KEYCLOAK_CLIENT_ID",
@@ -56,7 +59,7 @@ func init() {
5659
}
5760
}
5861

59-
keycloakClient, err = keycloak.NewKeycloakClient(testCtx, os.Getenv("KEYCLOAK_URL"), "", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), "", "", true, 5, "", false, userAgent, false, map[string]string{
62+
keycloakClient, err = keycloak.NewKeycloakClient(testCtx, os.Getenv("KEYCLOAK_URL"), "", os.Getenv("KEYCLOAK_CLIENT_ID"), os.Getenv("KEYCLOAK_CLIENT_SECRET"), os.Getenv("KEYCLOAK_REALM"), "", "", true, 5, "", "", "", false, userAgent, false, map[string]string{
6063
"foo": "bar",
6164
})
6265
if err != nil {

0 commit comments

Comments
 (0)