fix: oauth authorization support for azurecr (#451)

* fix: oauth authorization support for azurecr

* fix: oauth when destroying images

* refactor: run make fmt
This commit is contained in:
William Sedlacek 2022-09-09 04:59:18 -07:00 committed by GitHub
parent 0d82189137
commit d0eae33123
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 62 additions and 71 deletions

View file

@ -50,6 +50,12 @@ func isECRRepositoryURL(url string) bool {
return ecrRexp.MatchString(url) return ecrRexp.MatchString(url)
} }
func isAzureCRRepositoryURL(url string) bool {
// Regexp is based on the azurecr urls shown https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal?tabs=azure-cli#push-image-to-registry
var azurecrRexp = regexp.MustCompile(`^.*\.azurecr\.io$`)
return azurecrRexp.MatchString(url)
}
func setupHTTPHeadersForRegistryRequests(req *http.Request, fallback bool) { func setupHTTPHeadersForRegistryRequests(req *http.Request, fallback bool) {
// We accept schema v2 manifests and manifest lists, and also OCI types // We accept schema v2 manifests and manifest lists, and also OCI types
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json") req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")

View file

@ -81,7 +81,7 @@ func getImageDigest(registry string, registryWithProtocol string, image, tag, us
} }
if username != "" { if username != "" {
if registry != "ghcr.io" && !isECRRepositoryURL(registry) && registry != "gcr.io" { if registry != "ghcr.io" && !isECRRepositoryURL(registry) && !isAzureCRRepositoryURL(registry) && registry != "gcr.io" {
req.SetBasicAuth(username, password) req.SetBasicAuth(username, password)
} else { } else {
if isECRRepositoryURL(registry) { if isECRRepositoryURL(registry) {
@ -146,7 +146,8 @@ func getImageDigest(registry string, registryWithProtocol string, image, tag, us
} }
type TokenResponse struct { type TokenResponse struct {
Token string Token string
AccessToken string `json:"access_token"`
} }
// Parses key/value pairs from a WWW-Authenticate header // Parses key/value pairs from a WWW-Authenticate header
@ -214,7 +215,15 @@ func getAuthToken(authHeader string, username string, password string, client *h
return "", fmt.Errorf("Error parsing OAuth token response: %s", err) return "", fmt.Errorf("Error parsing OAuth token response: %s", err)
} }
return token.Token, nil if token.Token != "" {
return token.Token, nil
}
if token.AccessToken != "" {
return token.AccessToken, nil
}
return "", fmt.Errorf("Error unsupported OAuth response")
} }
func doDigestRequest(req *http.Request, client *http.Client) (*http.Response, error) { func doDigestRequest(req *http.Request, client *http.Client) (*http.Response, error) {

View file

@ -111,9 +111,9 @@ func TestAccDockerConfig_basicUpdatable(t *testing.T) {
}) })
} }
///////////// // ///////////
// Helpers // Helpers
///////////// // ///////////
func testCheckDockerConfigDestroy(ctx context.Context, s *terraform.State) error { func testCheckDockerConfigDestroy(ctx context.Context, s *terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient client := testAccProvider.Meta().(*ProviderConfig).DockerClient
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {

View file

@ -818,7 +818,6 @@ func TestAccDockerContainer_uploadSource(t *testing.T) {
}) })
} }
//
func TestAccDockerContainer_uploadSourceHash(t *testing.T) { func TestAccDockerContainer_uploadSourceHash(t *testing.T) {
var c types.ContainerJSON var c types.ContainerJSON
var firstRunId string var firstRunId string
@ -1606,9 +1605,9 @@ func TestAccDockerContainer_dualstackaddress(t *testing.T) {
}) })
} }
/////////// // /////////
// HELPERS // HELPERS
/////////// // /////////
func testAccContainerRunning(resourceName string, container *types.ContainerJSON) resource.TestCheckFunc { func testAccContainerRunning(resourceName string, container *types.ContainerJSON) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
ctx := context.Background() ctx := context.Background()

View file

@ -14,7 +14,6 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -483,7 +482,16 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithPr
} }
if username != "" { if username != "" {
req.SetBasicAuth(username, password) if pushOpts.Registry != "ghcr.io" && !isECRRepositoryURL(pushOpts.Registry) && !isAzureCRRepositoryURL(pushOpts.Registry) && pushOpts.Registry != "gcr.io" {
req.SetBasicAuth(username, password)
} else {
if isECRRepositoryURL(pushOpts.Registry) {
password = normalizeECRPasswordForHTTPUsage(password)
req.Header.Add("Authorization", "Basic "+password)
} else {
req.Header.Add("Authorization", "Bearer "+base64.StdEncoding.EncodeToString([]byte(password)))
}
}
} }
setupHTTPHeadersForRegistryRequests(req, fallback) setupHTTPHeadersForRegistryRequests(req, fallback)
@ -500,56 +508,26 @@ func deleteDockerRegistryImage(pushOpts internalPushImageOptions, registryWithPr
// Either OAuth is required or the basic auth creds were invalid // Either OAuth is required or the basic auth creds were invalid
case http.StatusUnauthorized: case http.StatusUnauthorized:
if strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") { if !strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") {
auth := parseAuthHeader(resp.Header.Get("www-authenticate")) return fmt.Errorf("Bad credentials: " + resp.Status)
params := url.Values{}
params.Set("service", auth["service"])
params.Set("scope", auth["scope"])
tokenRequest, err := http.NewRequest("GET", auth["realm"]+"?"+params.Encode(), nil)
if err != nil {
return fmt.Errorf("Error creating registry request: %s", err)
}
if username != "" {
tokenRequest.SetBasicAuth(username, password)
}
tokenResponse, err := client.Do(tokenRequest)
if err != nil {
return fmt.Errorf("Error during registry request: %s", err)
}
if tokenResponse.StatusCode != http.StatusOK {
return fmt.Errorf("Got bad response from registry: " + tokenResponse.Status)
}
body, err := ioutil.ReadAll(tokenResponse.Body)
if err != nil {
return fmt.Errorf("Error reading response body: %s", err)
}
token := &TokenResponse{}
err = json.Unmarshal(body, token)
if err != nil {
return fmt.Errorf("Error parsing OAuth token response: %s", err)
}
req.Header.Set("Authorization", "Bearer "+token.Token)
oauthResp, err := client.Do(req)
if err != nil {
return err
}
switch oauthResp.StatusCode {
case http.StatusOK, http.StatusAccepted, http.StatusNotFound:
return nil
default:
return fmt.Errorf("Got bad response from registry: " + resp.Status)
}
} }
return fmt.Errorf("Bad credentials: " + resp.Status) token, err := getAuthToken(resp.Header.Get("www-authenticate"), username, password, client)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+token)
oauthResp, err := client.Do(req)
if err != nil {
return err
}
switch oauthResp.StatusCode {
case http.StatusOK, http.StatusAccepted, http.StatusNotFound:
return nil
default:
return fmt.Errorf("Got bad response from registry: " + resp.Status)
}
// Some unexpected status was given, return an error // Some unexpected status was given, return an error
default: default:
return fmt.Errorf("Got bad response from registry: " + resp.Status) return fmt.Errorf("Got bad response from registry: " + resp.Status)

View file

@ -136,9 +136,9 @@ func TestAccDockerSecret_labels(t *testing.T) {
}) })
} }
///////////// // ///////////
// Helpers // Helpers
///////////// // ///////////
func testCheckDockerSecretDestroy(ctx context.Context, s *terraform.State) error { func testCheckDockerSecretDestroy(ctx context.Context, s *terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient client := testAccProvider.Meta().(*ProviderConfig).DockerClient
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {

View file

@ -25,9 +25,9 @@ type convergeConfig struct {
delay time.Duration delay time.Duration
} }
///////////////// // ///////////////
// TF CRUD funcs // TF CRUD funcs
///////////////// // ///////////////
func resourceDockerServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { func resourceDockerServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var err error var err error
client := meta.(*ProviderConfig).DockerClient client := meta.(*ProviderConfig).DockerClient
@ -226,9 +226,9 @@ func resourceDockerServiceDelete(ctx context.Context, d *schema.ResourceData, me
return nil return nil
} }
///////////////// // ///////////////
// Helpers // Helpers
///////////////// // ///////////////
// fetchDockerService fetches a service by its name or id // fetchDockerService fetches a service by its name or id
func fetchDockerService(ctx context.Context, ID string, name string, client *client.Client) (*swarm.Service, error) { func fetchDockerService(ctx context.Context, ID string, name string, client *client.Client) (*swarm.Service, error) {
apiServices, err := client.ServiceList(ctx, types.ServiceListOptions{}) apiServices, err := client.ServiceList(ctx, types.ServiceListOptions{})

View file

@ -13,10 +13,10 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
) )
////////////// // ////////////
// flatteners // flatteners
// flatten API objects to the terraform schema // flatten API objects to the terraform schema
////////////// // ////////////
// see https://learn.hashicorp.com/tutorials/terraform/provider-create?in=terraform/providers#add-flattening-functions // see https://learn.hashicorp.com/tutorials/terraform/provider-create?in=terraform/providers#add-flattening-functions
func flattenTaskSpec(in swarm.TaskSpec) []interface{} { func flattenTaskSpec(in swarm.TaskSpec) []interface{} {
m := make(map[string]interface{}) m := make(map[string]interface{})
@ -110,7 +110,7 @@ func flattenServiceEndpointSpec(in *swarm.EndpointSpec) []interface{} {
return out return out
} }
///// start TaskSpec // /// start TaskSpec
func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} { func flattenContainerSpec(in *swarm.ContainerSpec) []interface{} {
out := make([]interface{}, 0) out := make([]interface{}, 0)
m := make(map[string]interface{}) m := make(map[string]interface{})
@ -525,8 +525,8 @@ func flattenTaskLogDriver(in *swarm.Driver) []interface{} {
return out return out
} }
///// end TaskSpec // /// end TaskSpec
///// start EndpointSpec // /// start EndpointSpec
func flattenServicePorts(in []swarm.PortConfig) []interface{} { func flattenServicePorts(in []swarm.PortConfig) []interface{} {
out := make([]interface{}, len(in)) out := make([]interface{}, len(in))
for i, v := range in { for i, v := range in {
@ -543,10 +543,10 @@ func flattenServicePorts(in []swarm.PortConfig) []interface{} {
///// end EndpointSpec ///// end EndpointSpec
////////////// // ////////////
// Mappers // Mappers
// create API object from the terraform resource schema // create API object from the terraform resource schema
////////////// // ////////////
// createServiceSpec creates the service spec: https://docs.docker.com/engine/api/v1.32/#operation/ServiceCreate // createServiceSpec creates the service spec: https://docs.docker.com/engine/api/v1.32/#operation/ServiceCreate
func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) { func createServiceSpec(d *schema.ResourceData) (swarm.ServiceSpec, error) {
serviceSpec := swarm.ServiceSpec{ serviceSpec := swarm.ServiceSpec{

View file

@ -38,7 +38,6 @@ const (
// As a convention the test configurations are in // As a convention the test configurations are in
// 'testdata/<resourceType>/<resourceName>/<testName>.tf', e.g. // 'testdata/<resourceType>/<resourceName>/<testName>.tf', e.g.
// 'testdata/resources/docker_container/testAccDockerContainerPrivateImage.tf' // 'testdata/resources/docker_container/testAccDockerContainerPrivateImage.tf'
//
func loadTestConfiguration(t *testing.T, resourceType resourceType, resourceName, testName string) string { func loadTestConfiguration(t *testing.T, resourceType resourceType, resourceName, testName string) string {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {