Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provider: Add mTLS support #82

Merged
merged 1 commit into from
Mar 10, 2024
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
2 changes: 2 additions & 0 deletions docs/data-sources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ Required:
Optional:

- `pending` (List of String) The expected status sentinels for pending status.


26 changes: 24 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,38 @@ provider "restful" {

### Optional

- `cookie_enabled` (Boolean) Save cookies during API contracting. Defaults to `false`.
- `client` (Attributes) The client configuration (see [below for nested schema](#nestedatt--client))
- `create_method` (String) The method used to create the resource. Possible values are `PUT` and `POST`. Defaults to `POST`.
- `delete_method` (String) The method used to delete the resource. Possible values are `DELETE` and `POST`. Defaults to `DELETE`.
- `header` (Map of String) The header parameters that are applied to each request.
- `merge_patch_disabled` (Boolean) Whether to use a JSON Merge Patch as the request body in the PATCH update? Defaults to `false`. This is only effective when `update_method` is set to `PATCH`.
- `query` (Map of List of String) The query parameters that are applied to each request.
- `security` (Attributes) The OpenAPI security scheme that is be used for auth. Only one of `http`, `apikey` and `oauth2` can be specified. (see [below for nested schema](#nestedatt--security))
- `tls_insecure_skip_verify` (Boolean) Whether a client verifies the server's certificate chain and host name. Defaults to `false`.
- `update_method` (String) The method used to update the resource. Possible values are `PUT` and `PATCH`. Defaults to `PUT`.

<a id="nestedatt--client"></a>
### Nested Schema for `client`

Optional:

- `certificates` (Attributes List) The client certificates for mTLS. (see [below for nested schema](#nestedatt--client--certificates))
- `cookie_enabled` (Boolean) Save cookies during API contracting. Defaults to `false`.
- `root_ca_certificate_files` (List of String) The list of certificate file paths of root certificate authorities that clients use when verifying server certificates. If not specified, TLS uses the host's root CA set. Conflicts with `root_ca_certificate_files`.
- `root_ca_certificates` (List of String) The list of certificates of root certificate authorities that clients use when verifying server certificates. If not specified, TLS uses the host's root CA set. Conflicts with `root_ca_certificate_files`.
- `tls_insecure_skip_verify` (Boolean) Whether a client verifies the server's certificate chain and host name. Defaults to `false`.

<a id="nestedatt--client--certificates"></a>
### Nested Schema for `client.certificates`

Optional:

- `certificate` (String) The client certificate for mTLS. Conflicts with `certificate_file`. Requires `key_file` or `key`.
- `certificate_file` (String) The path of the client certificate file for mTLS. Conflicts with `certificate`. Requires `key_file` or `key`.
- `key` (String) The client private key for mTLS. Conflicts with `key_file`.
- `key_file` (String) The path of the client private key file for mTLS. Conflicts with `key`. Requires `certificate_file` or `certificate`.



<a id="nestedatt--security"></a>
### Nested Schema for `security`

Expand Down
2 changes: 2 additions & 0 deletions docs/resources/operation.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,5 @@ Required:
Optional:

- `pending` (List of String) The expected status sentinels for pending status.


51 changes: 29 additions & 22 deletions internal/client/build_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,27 @@ type BuildOption struct {
}

type securityOption interface {
newClient(ctx context.Context, client *http.Client) (*resty.Client, error)
configureClient(ctx context.Context, client *resty.Client) error
}

type HTTPBasicOption struct {
Username string
Password string
}

func (opt HTTPBasicOption) newClient(_ context.Context, client *http.Client) (*resty.Client, error) {
return resty.NewWithClient(client).SetBasicAuth(opt.Username, opt.Password), nil
func (opt HTTPBasicOption) configureClient(_ context.Context, client *resty.Client) error {
client.SetBasicAuth(opt.Username, opt.Password)
return nil
}

type HTTPTokenOption struct {
Token string
Scheme string
}

func (opt HTTPTokenOption) newClient(_ context.Context, client *http.Client) (*resty.Client, error) {
return resty.NewWithClient(client).SetAuthToken(opt.Token).SetScheme(opt.Scheme), nil
func (opt HTTPTokenOption) configureClient(_ context.Context, client *resty.Client) error {
client.SetAuthToken(opt.Token).SetScheme(opt.Scheme)
return nil
}

type APIKeyAuthIn string
Expand All @@ -55,22 +57,21 @@ type APIKeyAuthOpt struct {

type APIKeyAuthOption []APIKeyAuthOpt

func (opt APIKeyAuthOption) newClient(_ context.Context, client *http.Client) (*resty.Client, error) {
c := resty.NewWithClient(client)
func (opt APIKeyAuthOption) configureClient(_ context.Context, client *resty.Client) error {
for _, key := range opt {
switch key.In {
case APIKeyAuthInHeader:
c.SetHeader(key.Name, key.Value)
client.SetHeader(key.Name, key.Value)
case APIKeyAuthInQuery:
c.SetQueryParam(key.Name, key.Value)
client.SetQueryParam(key.Name, key.Value)
case APIKeyAuthInCookie:
c.SetCookie(&http.Cookie{
client.SetCookie(&http.Cookie{
Name: key.Name,
Value: key.Value,
})
}
}
return c, nil
return nil
}

type OAuth2AuthStyle string
Expand All @@ -91,7 +92,7 @@ type OAuth2PasswordOption struct {
Scopes []string
}

func (opt OAuth2PasswordOption) newClient(ctx context.Context, client *http.Client) (*resty.Client, error) {
func (opt OAuth2PasswordOption) configureClient(ctx context.Context, client *resty.Client) error {
cfg := oauth2.Config{
ClientID: opt.ClientId,
ClientSecret: opt.ClientSecret,
Expand All @@ -110,16 +111,18 @@ func (opt OAuth2PasswordOption) newClient(ctx context.Context, client *http.Clie

tk, err := cfg.PasswordCredentialsToken(ctx, opt.Username, opt.Password)
if err != nil {
return nil, err
return err
}

// We use background context here when constructing the client since we are building the client during the provider configuration, where the context is used only for that purpose.
// Especially, when we use this client, we will set the operation bound context for each request.
ctx = context.WithValue(context.Background(), oauth2.HTTPClient, client)
httpClient := client.GetClient()
ctx = context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)

// We use background context here when constructing the client since we are building the client during the provider configuration, where the context is used only for that purpose.
// Especially, when we use this client, we will set the operation bound context for each request.
return resty.NewWithClient(cfg.Client(ctx, tk)), nil
*client = *resty.NewWithClient(cfg.Client(ctx, tk))
return nil
}

type OAuth2ClientCredentialOption struct {
Expand All @@ -132,7 +135,7 @@ type OAuth2ClientCredentialOption struct {
AuthStyle OAuth2AuthStyle
}

func (opt OAuth2ClientCredentialOption) newClient(_ context.Context, client *http.Client) (*resty.Client, error) {
func (opt OAuth2ClientCredentialOption) configureClient(_ context.Context, client *resty.Client) error {
cfg := clientcredentials.Config{
ClientID: opt.ClientId,
ClientSecret: opt.ClientSecret,
Expand All @@ -150,9 +153,11 @@ func (opt OAuth2ClientCredentialOption) newClient(_ context.Context, client *htt

// We use background context here when constructing the client since we are building the client during the provider configuration, where the context is used only for that purpose.
// Especially, when we use this client, we will set the operation bound context for each request.
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, client)
httpClient := client.GetClient()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
ts := cfg.TokenSource(ctx)
return resty.NewWithClient(oauth2.NewClient(ctx, ts)), nil
*client = *resty.NewWithClient(oauth2.NewClient(ctx, ts))
return nil
}

type OAuth2RefreshTokenOption struct {
Expand All @@ -166,7 +171,7 @@ type OAuth2RefreshTokenOption struct {
Scopes []string
}

func (opt OAuth2RefreshTokenOption) newClient(_ context.Context, client *http.Client) (*resty.Client, error) {
func (opt OAuth2RefreshTokenOption) configureClient(_ context.Context, client *resty.Client) error {
cfg := oauth2.Config{
ClientID: opt.ClientId,
ClientSecret: opt.ClientSecret,
Expand All @@ -185,10 +190,12 @@ func (opt OAuth2RefreshTokenOption) newClient(_ context.Context, client *http.Cl

// We use background context here when constructing the client since we are building the client during the provider configuration, where the context is used only for that purpose.
// Especially, when we use this client, we will set the operation bound context for each request.
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, client)
return resty.NewWithClient(cfg.Client(ctx, &oauth2.Token{
httpClient := client.GetClient()
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)
*client = *resty.NewWithClient(cfg.Client(ctx, &oauth2.Token{
RefreshToken: opt.RefreshToken,
TokenType: opt.TokenType,
Expiry: time.Now(),
})), nil
}))
return nil
}
6 changes: 2 additions & 4 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,9 @@ func New(ctx context.Context, baseURL string, opt *BuildOption) (*Client, error)
httpClient.Jar = cookieJar
}

client := resty.New()
client := resty.NewWithClient(httpClient)
if opt.Security != nil {
var err error
client, err = opt.Security.newClient(ctx, httpClient)
if err != nil {
if err := opt.Security.configureClient(ctx, client); err != nil {
return nil, err
}
}
Expand Down
Loading