actions-runner-controller/vault/azurekeyvault/config.go

121 lines
3.2 KiB
Go

package azurekeyvault
import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets"
"github.com/hashicorp/go-retryablehttp"
"golang.org/x/net/http/httpproxy"
)
// AzureKeyVault is a struct that holds the Azure Key Vault client.
type Config struct {
TenantID string `json:"tenant_id"`
ClientID string `json:"client_id"`
URL string `json:"url"`
CertificatePath string `json:"certificate_path"`
Proxy *httpproxy.Config `json:"proxy,omitempty"`
}
func (c *Config) Validate() error {
if c.TenantID == "" {
return errors.New("tenant_id is not set")
}
if c.ClientID == "" {
return errors.New("client_id is not set")
}
if _, err := url.ParseRequestURI(c.URL); err != nil {
return fmt.Errorf("failed to parse url: %v", err)
}
if c.CertificatePath == "" {
return errors.New("cert path must be provided")
}
if _, err := os.Stat(c.CertificatePath); err != nil {
return fmt.Errorf("cert path %q does not exist: %v", c.CertificatePath, err)
}
if c.Proxy != nil {
if c.Proxy.HTTPProxy == "" && c.Proxy.HTTPSProxy == "" && c.Proxy.NoProxy == "" {
return errors.New("proxy configuration is empty, at least one proxy must be set")
}
}
return nil
}
// Client creates a new Azure Key Vault client using the provided configuration.
func (c *Config) Client() (*azsecrets.Client, error) {
return c.certClient()
}
func (c *Config) certClient() (*azsecrets.Client, error) {
data, err := os.ReadFile(c.CertificatePath)
if err != nil {
return nil, fmt.Errorf("failed to read cert file from path %q: %v", c.CertificatePath, err)
}
certs, key, err := azidentity.ParseCertificates(data, nil)
if err != nil {
return nil, fmt.Errorf("failed to parse certificates: %w", err)
}
httpClient, err := c.httpClient()
if err != nil {
return nil, fmt.Errorf("failed to instantiate http client: %v", err)
}
cred, err := azidentity.NewClientCertificateCredential(
c.TenantID,
c.ClientID,
certs,
key,
&azidentity.ClientCertificateCredentialOptions{
ClientOptions: policy.ClientOptions{
Transport: httpClient,
},
},
)
if err != nil {
return nil, fmt.Errorf("failed to create client certificate credential: %v", err)
}
client, err := azsecrets.NewClient(c.URL, cred, &azsecrets.ClientOptions{
ClientOptions: policy.ClientOptions{
Transport: httpClient,
},
})
if err != nil {
return nil, fmt.Errorf("failed to instantiate client for azsecrets: %v", err)
}
return client, nil
}
func (c *Config) httpClient() (*http.Client, error) {
retryClient := retryablehttp.NewClient()
retryClient.RetryMax = 4
retryClient.RetryWaitMax = 30 * time.Second
retryClient.HTTPClient.Timeout = 5 * time.Minute
transport, ok := retryClient.HTTPClient.Transport.(*http.Transport)
if !ok {
return nil, fmt.Errorf("failed to get http transport")
}
if c.Proxy != nil {
transport.Proxy = func(req *http.Request) (*url.URL, error) {
return c.Proxy.ProxyFunc()(req.URL)
}
}
return retryClient.StandardClient(), nil
}