Skip to content

Commit 4a059dc

Browse files
authored
security: GH-31 Configuring TLS via Provider Config (#40)
* security: GH-31 Adding tls_skip_verify to provider config * security: GH-31 Updating documentation and changelog
1 parent 1695bb4 commit 4a059dc

7 files changed

Lines changed: 193 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
## 0.2.0
2+
3+
FEATURES:
4+
- Added `tls_skip_verify` option to provider config.
5+
16
## 0.1.2
27

8+
FEATURES:
39
- Updating documentation. No functional changes.
410

511
## 0.1.1

docs/index.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ For authentication, the provider requires you to supply the following informatio
1717
- Host
1818
- Client ID
1919
- Client Secret
20-
- Customer or MSP ID (also referred to as the Omada ID)
20+
- Controller ID (also referred to as the Omada ID)
2121

2222
For detailed instructions on how to obtain these from your Software Controller, [see here](https://support.omadanetworks.com/uk/document/109315#_Toc212122902).
2323

@@ -27,9 +27,11 @@ You may also choose to set these to the following environment variables respecti
2727
- `OMADA_CLIENT_ID`
2828
- `OMADA_CLIENT_SECRET`
2929
- `OMADA_CONTROLLER_ID`
30+
- `OMADA_TLS_SKIP_VERIFY`
3031

3132
-> If you are self-hosting your Software Controller, the host must be resolvable from wherever your Terraform configuration is deployed.
32-
For example, in your local network you may set your host to `https://192.168.0.10:8043`
33+
For example, in your local network you may set your host to `https://192.168.0.10:8043`.
34+
You may also need to set `tls_skip_verify` to `true` if your Software Controller has self-signed certificates. However this is not advisable if you will be deploying the configuration to a production environment.
3335

3436
## Example Usage
3537

@@ -57,6 +59,9 @@ provider "omada" {
5759
# Alternatively, omit this field and supply it securely
5860
# with the OMADA_CLIENT_SECRET environment variable
5961
client_secret = var.client_secret
62+
63+
# Explicitly set the TLS verification setting
64+
tls_skip_verify = false
6065
}
6166
```
6267

@@ -68,4 +73,6 @@ provider "omada" {
6873
- `client_id` (String) Client ID for the Omada Controller Application. May also be provided via `OMADA_CLIENT_ID` environment variable.
6974
- `client_secret` (String, Sensitive) Client Secret for the Omada Controller Application. May also be provided via `OMADA_CLIENT_SECRET` environment variable.
7075
- `controller_id` (String) Unique ID assigned to the Omada Controller. May also be provided via `OMADA_CONTROLLER_ID` environment variable.
71-
- `host` (String) URI for the Omada Controller API. May also be provided via `OMADA_HOST` environment variable.
76+
- `host` (String) URI for the Omada Controller API. May also be provided via `OMADA_HOST` environment variable.
77+
- `tls_skip_verify` (Boolean) When set to true, accepts any certificate presented by the server and any host name in that certificate.
78+
**It is unadvisable to use this in a production environment, as it makes the provider susceptible to man-in-the-middle attacks.**

examples/provider/provider.tf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ provider "omada" {
2121
# Alternatively, omit this field and supply it securely
2222
# with the OMADA_CLIENT_SECRET environment variable
2323
client_secret = var.client_secret
24+
25+
# Explicitly set the TLS verification setting
26+
tls_skip_verify = false
2427
}

internal/acctest/acctest.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ provider "omada" {
2121
`, host, TestControllerID)
2222
}
2323

24+
func providerConfigWithTLSSkipVerify(host string, tlsSkipVerify bool) string {
25+
return fmt.Sprintf(`
26+
provider "omada" {
27+
host = %q
28+
controller_id = %q
29+
client_id = "test-client-id"
30+
client_secret = "test-client-secret"
31+
tls_skip_verify = %t
32+
}
33+
`, host, TestControllerID, tlsSkipVerify)
34+
}
35+
2436
// TestServer is a configurable httptest-backed Omada API stand-in.
2537
//
2638
// TokenHandler is invoked for POST /openapi/authorize/token requests. Tests may
@@ -62,3 +74,30 @@ func NewTestServer(t *testing.T) *TestServer {
6274
ts.ProviderConfig = providerConfig(server.URL)
6375
return ts
6476
}
77+
78+
// NewTLSTestServer is like NewTestServer but serves HTTPS using httptest's
79+
// auto-generated self-signed certificate. Use it to exercise the provider's
80+
// tls_skip_verify behavior. The default ProviderConfig sets
81+
// tls_skip_verify = true so the test server is reachable; use
82+
// ProviderConfigWithTLSSkipVerify(false) to assert verification failures.
83+
func NewTLSTestServer(t *testing.T) *TestServer {
84+
t.Helper()
85+
ts := &TestServer{
86+
Mux: http.NewServeMux(),
87+
TokenHandler: defaultTokenHandler,
88+
}
89+
ts.Mux.HandleFunc("POST /openapi/authorize/token", func(w http.ResponseWriter, r *http.Request) {
90+
ts.TokenHandler(w, r)
91+
})
92+
server := httptest.NewTLSServer(ts.Mux)
93+
t.Cleanup(server.Close)
94+
ts.URL = server.URL
95+
ts.ProviderConfig = providerConfigWithTLSSkipVerify(server.URL, true)
96+
return ts
97+
}
98+
99+
// ProviderConfigWithTLSSkipVerify returns a provider configuration block for
100+
// this test server with tls_skip_verify set to the given value.
101+
func (ts *TestServer) ProviderConfigWithTLSSkipVerify(tlsSkipVerify bool) string {
102+
return providerConfigWithTLSSkipVerify(ts.URL, tlsSkipVerify)
103+
}

internal/provider/provider.go

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/tls"
66
"net/http"
77
"os"
8+
"strconv"
89
"terraform-provider-omada/internal/client"
910
"terraform-provider-omada/internal/service/site"
1011

@@ -41,10 +42,11 @@ type omadaProvider struct {
4142

4243
// omadaProviderModel maps provider schema data to a Go type.
4344
type omadaProviderModel struct {
44-
Host types.String `tfsdk:"host"`
45-
ControllerId types.String `tfsdk:"controller_id"`
46-
ClientId types.String `tfsdk:"client_id"`
47-
ClientSecret types.String `tfsdk:"client_secret"`
45+
Host types.String `tfsdk:"host"`
46+
ControllerId types.String `tfsdk:"controller_id"`
47+
ClientId types.String `tfsdk:"client_id"`
48+
ClientSecret types.String `tfsdk:"client_secret"`
49+
TlsSkipVerify types.Bool `tfsdk:"tls_skip_verify"`
4850
}
4951

5052
// Metadata returns the provider type name.
@@ -75,6 +77,11 @@ func (p *omadaProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp
7577
Optional: true,
7678
Sensitive: true,
7779
},
80+
"tls_skip_verify": schema.BoolAttribute{
81+
Description: `When set to true, accepts any certificate presented by the server and any host name in that certificate.
82+
**It is unadvisable to use this in a production environment, as it makes the provider susceptible to man-in-the-middle attacks.**`,
83+
Optional: true,
84+
},
7885
},
7986
}
8087
}
@@ -130,6 +137,15 @@ func (p *omadaProvider) Configure(ctx context.Context, req provider.ConfigureReq
130137
)
131138
}
132139

140+
if config.TlsSkipVerify.IsUnknown() {
141+
resp.Diagnostics.AddAttributeError(
142+
path.Root("tls_skip_verify"),
143+
"Unknown value for tls_skip_verify",
144+
"The provider cannot create the Omada API client as there is an unknown configuration value for the TLS verification. "+
145+
"Either target apply the source of the value first, set the value statically in the configuration, or use the OMADA_TLS_SKIP_VERIFY environment variable.",
146+
)
147+
}
148+
133149
if resp.Diagnostics.HasError() {
134150
return
135151
}
@@ -142,6 +158,9 @@ func (p *omadaProvider) Configure(ctx context.Context, req provider.ConfigureReq
142158
client_id := os.Getenv("OMADA_CLIENT_ID")
143159
client_secret := os.Getenv("OMADA_CLIENT_SECRET")
144160

161+
// Read and parse the env variable as a boolean.
162+
tls_skip_verify, parse_err := strconv.ParseBool(os.Getenv("OMADA_TLS_SKIP_VERIFY"))
163+
145164
if !config.Host.IsNull() {
146165
host = config.Host.ValueString()
147166
}
@@ -158,6 +177,10 @@ func (p *omadaProvider) Configure(ctx context.Context, req provider.ConfigureReq
158177
client_secret = config.ClientSecret.ValueString()
159178
}
160179

180+
if !config.TlsSkipVerify.IsNull() {
181+
tls_skip_verify = config.TlsSkipVerify.ValueBool()
182+
}
183+
161184
// If any of the expected configurations are missing, return
162185
// errors with provider-specific guidance.
163186

@@ -201,6 +224,11 @@ func (p *omadaProvider) Configure(ctx context.Context, req provider.ConfigureReq
201224
)
202225
}
203226

227+
// We don't need to add an error, we will just consider the tls_skip_verify as false
228+
if parse_err != nil {
229+
tls_skip_verify = false
230+
}
231+
204232
if resp.Diagnostics.HasError() {
205233
return
206234
}
@@ -215,8 +243,7 @@ func (p *omadaProvider) Configure(ctx context.Context, req provider.ConfigureReq
215243

216244
httpClient := &http.Client{
217245
Transport: &http.Transport{
218-
// TODO: Remove this when testing environment has SSL certification configured
219-
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
246+
TLSClientConfig: &tls.Config{InsecureSkipVerify: tls_skip_verify},
220247
},
221248
}
222249

internal/provider/provider_test.go

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provider
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"net/http"
67
"regexp"
78
"terraform-provider-omada/internal/acctest"
@@ -16,19 +17,22 @@ var ProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, erro
1617
"omada": providerserver.NewProtocol6WithError(New("test")()),
1718
}
1819

19-
func Test_Provider_NoInlineConfig(t *testing.T) {
20+
func Test_Provider_NoHost(t *testing.T) {
2021
// Ensure env-var fallbacks don't satisfy the provider.
2122
t.Setenv("OMADA_HOST", "")
2223
t.Setenv("OMADA_CONTROLLER_ID", "")
2324
t.Setenv("OMADA_CLIENT_ID", "")
2425
t.Setenv("OMADA_CLIENT_SECRET", "")
26+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "")
2527

2628
resource.UnitTest(t, resource.TestCase{
2729
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
2830
Steps: []resource.TestStep{
2931
{
3032
Config: `
31-
provider "omada" {}
33+
provider "omada" {
34+
tls_skip_verify = true
35+
}
3236
3337
data "omada_sites" "test" {}
3438
`,
@@ -44,14 +48,16 @@ func Test_Provider_NoControllerId(t *testing.T) {
4448
t.Setenv("OMADA_CONTROLLER_ID", "")
4549
t.Setenv("OMADA_CLIENT_ID", "")
4650
t.Setenv("OMADA_CLIENT_SECRET", "")
51+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "")
4752

4853
resource.UnitTest(t, resource.TestCase{
4954
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
5055
Steps: []resource.TestStep{
5156
{
5257
Config: `
5358
provider "omada" {
54-
host = "https://example.com"
59+
host = "https://example.com"
60+
tls_skip_verify = true
5561
}
5662
5763
data "omada_sites" "test" {}
@@ -68,15 +74,17 @@ func Test_Provider_NoClientId(t *testing.T) {
6874
t.Setenv("OMADA_CONTROLLER_ID", "")
6975
t.Setenv("OMADA_CLIENT_ID", "")
7076
t.Setenv("OMADA_CLIENT_SECRET", "")
77+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "")
7178

7279
resource.UnitTest(t, resource.TestCase{
7380
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
7481
Steps: []resource.TestStep{
7582
{
7683
Config: `
7784
provider "omada" {
78-
host = "https://example.com"
79-
controller_id = "test-controller-id"
85+
host = "https://example.com"
86+
controller_id = "test-controller-id"
87+
tls_skip_verify = true
8088
}
8189
8290
data "omada_sites" "test" {}
@@ -93,16 +101,18 @@ func Test_Provider_NoClientSecret(t *testing.T) {
93101
t.Setenv("OMADA_CONTROLLER_ID", "")
94102
t.Setenv("OMADA_CLIENT_ID", "")
95103
t.Setenv("OMADA_CLIENT_SECRET", "")
104+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "")
96105

97106
resource.UnitTest(t, resource.TestCase{
98107
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
99108
Steps: []resource.TestStep{
100109
{
101110
Config: `
102111
provider "omada" {
103-
host = "https://example.com"
104-
controller_id = "test-controller-id"
105-
client_id = "test-client-id"
112+
host = "https://example.com"
113+
controller_id = "test-controller-id"
114+
client_id = "test-client-id"
115+
tls_skip_verify = true
106116
}
107117
108118
data "omada_sites" "test" {}
@@ -134,3 +144,83 @@ func TestAcc_Provider_ClientCreationFailed(t *testing.T) {
134144
},
135145
})
136146
}
147+
148+
// Ensures that when tls_skip_verify is false the provider refuses a self-signed certificate.
149+
func TestAcc_Provider_TLSSkipVerify_False_RejectsSelfSigned(t *testing.T) {
150+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "")
151+
152+
ts := acctest.NewTLSTestServer(t)
153+
154+
resource.Test(t, resource.TestCase{
155+
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
156+
Steps: []resource.TestStep{
157+
{
158+
Config: ts.ProviderConfigWithTLSSkipVerify(false) + `data "omada_sites" "test" {}`,
159+
ExpectError: regexp.MustCompile(`(?i)x509|certificate signed by unknown authority|tls`),
160+
},
161+
},
162+
})
163+
}
164+
165+
// TestAcc_Provider_TLSSkipVerify_True_AcceptsSelfSigned ensures that when
166+
// tls_skip_verify is true the provider successfully completes the TLS
167+
// handshake against a self-signed certificate. We force a known controller-
168+
// side error so the test asserts the request reached the server (i.e. the
169+
// handshake succeeded) without needing to stub the entire API surface.
170+
func TestAcc_Provider_TLSSkipVerify_True_AcceptsSelfSigned(t *testing.T) {
171+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "")
172+
173+
ts := acctest.NewTLSTestServer(t)
174+
ts.TokenHandler = func(w http.ResponseWriter, _ *http.Request) {
175+
w.Header().Set("Content-Type", "application/json")
176+
_ = json.NewEncoder(w).Encode(map[string]any{
177+
"errorCode": -1,
178+
"msg": "No access token found",
179+
})
180+
}
181+
182+
resource.Test(t, resource.TestCase{
183+
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
184+
Steps: []resource.TestStep{
185+
{
186+
Config: ts.ProviderConfig + `data "omada_sites" "test" {}`,
187+
ExpectError: regexp.MustCompile("Unable to create Omada API Client"),
188+
},
189+
},
190+
})
191+
}
192+
193+
// TestAcc_Provider_TLSSkipVerify_EnvVar ensures the OMADA_TLS_SKIP_VERIFY env
194+
// var is honored when the attribute is omitted from configuration.
195+
func TestAcc_Provider_TLSSkipVerify_EnvVar(t *testing.T) {
196+
t.Setenv("OMADA_TLS_SKIP_VERIFY", "true")
197+
198+
ts := acctest.NewTLSTestServer(t)
199+
ts.TokenHandler = func(w http.ResponseWriter, _ *http.Request) {
200+
w.Header().Set("Content-Type", "application/json")
201+
_ = json.NewEncoder(w).Encode(map[string]any{
202+
"errorCode": -1,
203+
"msg": "No access token found",
204+
})
205+
}
206+
207+
// Build a provider config that omits tls_skip_verify so the env var is used.
208+
cfg := fmt.Sprintf(`
209+
provider "omada" {
210+
host = %q
211+
controller_id = %q
212+
client_id = "test-client-id"
213+
client_secret = "test-client-secret"
214+
}
215+
`, ts.URL, acctest.TestControllerID)
216+
217+
resource.Test(t, resource.TestCase{
218+
ProtoV6ProviderFactories: ProtoV6ProviderFactories,
219+
Steps: []resource.TestStep{
220+
{
221+
Config: cfg + `data "omada_sites" "test" {}`,
222+
ExpectError: regexp.MustCompile("Unable to create Omada API Client"),
223+
},
224+
},
225+
})
226+
}

0 commit comments

Comments
 (0)