forked from timstclair/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Rudi Chiarito
committed
Jan 22, 2016
1 parent
aa5e3ab
commit bc0dd97
Showing
6 changed files
with
307 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* | ||
Copyright 2014 The Kubernetes Authors All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package aws_credentials | ||
|
||
import ( | ||
"encoding/base64" | ||
"strings" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/request" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/ecr" | ||
"github.com/golang/glog" | ||
"k8s.io/kubernetes/pkg/cloudprovider" | ||
aws_cloud "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" | ||
"k8s.io/kubernetes/pkg/credentialprovider" | ||
) | ||
|
||
var registryUrls = []string{"*.dkr.ecr.*.amazonaws.com"} | ||
|
||
// awsHandlerLogger is a handler that logs all AWS SDK requests | ||
// Copied from cloudprovider/aws/log_handler.go | ||
func awsHandlerLogger(req *request.Request) { | ||
service := req.ClientInfo.ServiceName | ||
|
||
name := "?" | ||
if req.Operation != nil { | ||
name = req.Operation.Name | ||
} | ||
|
||
glog.V(4).Infof("AWS request: %s %s", service, name) | ||
} | ||
|
||
// An interface for testing purposes. | ||
type tokenGetter interface { | ||
GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error) | ||
} | ||
|
||
// The canonical implementation | ||
type ecrTokenGetter struct { | ||
svc *ecr.ECR | ||
} | ||
|
||
func (p *ecrTokenGetter) GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error) { | ||
return p.svc.GetAuthorizationToken(input) | ||
} | ||
|
||
// ecrProvider is a DockerConfigProvider that gets and refreshes 12-hour tokens | ||
// from AWS to access ECR. | ||
type ecrProvider struct { | ||
getter tokenGetter | ||
} | ||
|
||
// init registers the various means by which ECR credentials may | ||
// be resolved. | ||
func init() { | ||
credentialprovider.RegisterCredentialProvider("aws-ecr-key", | ||
&credentialprovider.CachingDockerConfigProvider{ | ||
Provider: &ecrProvider{}, | ||
// Refresh credentials a little earlier before they expire | ||
Lifetime: 11*time.Hour + 55*time.Minute, | ||
}) | ||
} | ||
|
||
// Enabled implements DockerConfigProvider.Enabled for the AWS token-based implementation. | ||
// For now, it gets activated only if AWS was chosen as the cloud provider. | ||
// TODO: figure how to enable it manually for deployments that are not on AWS but still | ||
// use ECR somehow? | ||
func (p *ecrProvider) Enabled() bool { | ||
provider, err := cloudprovider.GetCloudProvider(aws_cloud.ProviderName, nil) | ||
if err != nil { | ||
glog.Errorf("while initializing AWS cloud provider %v", err) | ||
return false | ||
} | ||
if provider == nil { | ||
return false | ||
} | ||
|
||
zones, ok := provider.Zones() | ||
if !ok { | ||
glog.Errorf("couldn't get Zones() interface") | ||
return false | ||
} | ||
zone, err := zones.GetZone() | ||
if err != nil { | ||
glog.Errorf("while getting zone %v", err) | ||
return false | ||
} | ||
if zone.Region == "" { | ||
glog.Errorf("Region information is empty") | ||
return false | ||
} | ||
|
||
getter := &ecrTokenGetter{svc: ecr.New(session.New(&aws.Config{ | ||
Credentials: nil, | ||
Region: &zone.Region, | ||
}))} | ||
getter.svc.Handlers.Sign.PushFrontNamed(request.NamedHandler{ | ||
Name: "k8s/logger", | ||
Fn: awsHandlerLogger, | ||
}) | ||
p.getter = getter | ||
|
||
return true | ||
} | ||
|
||
// Provide implements DockerConfigProvider.Provide, refreshing ECR tokens on demand | ||
func (p *ecrProvider) Provide() credentialprovider.DockerConfig { | ||
cfg := credentialprovider.DockerConfig{} | ||
|
||
// TODO: fill in RegistryIds? | ||
params := &ecr.GetAuthorizationTokenInput{} | ||
output, err := p.getter.GetAuthorizationToken(params) | ||
if err != nil { | ||
glog.Errorf("while requesting ECR authorization token %v", err) | ||
return cfg | ||
} | ||
if output == nil { | ||
glog.Errorf("Got back no ECR token") | ||
return cfg | ||
} | ||
|
||
for _, data := range output.AuthorizationData { | ||
if data.ProxyEndpoint != nil && | ||
data.AuthorizationToken != nil { | ||
decodedToken, err := base64.StdEncoding.DecodeString(aws.StringValue(data.AuthorizationToken)) | ||
if err != nil { | ||
glog.Errorf("while decoding token for endpoint %s %v", data.ProxyEndpoint, err) | ||
return cfg | ||
} | ||
parts := strings.SplitN(string(decodedToken), ":", 2) | ||
user := parts[0] | ||
password := parts[1] | ||
entry := credentialprovider.DockerConfigEntry{ | ||
Username: user, | ||
Password: password, | ||
// ECR doesn't care and Docker is about to obsolete it | ||
Email: "[email protected]", | ||
} | ||
|
||
// Add our entry for each of the supported container registry URLs | ||
for _, k := range registryUrls { | ||
cfg[k] = entry | ||
} | ||
} | ||
} | ||
return cfg | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
/* | ||
Copyright 2014 The Kubernetes Authors All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package aws_credentials | ||
|
||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
"path" | ||
"testing" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/ecr" | ||
|
||
"k8s.io/kubernetes/pkg/credentialprovider" | ||
) | ||
|
||
const user = "foo" | ||
const password = "1234567890abcdef" | ||
const email = "[email protected]" | ||
|
||
// Mock implementation | ||
type testTokenGetter struct { | ||
user string | ||
password string | ||
endpoint string | ||
} | ||
|
||
func (p *testTokenGetter) GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error) { | ||
|
||
expiration := time.Now().Add(1 * time.Hour) | ||
creds := []byte(fmt.Sprintf("%s:%s", p.user, p.password)) | ||
data := &ecr.AuthorizationData{ | ||
AuthorizationToken: aws.String(base64.StdEncoding.EncodeToString(creds)), | ||
ExpiresAt: &expiration, | ||
ProxyEndpoint: aws.String(p.endpoint), | ||
} | ||
output := &ecr.GetAuthorizationTokenOutput{ | ||
AuthorizationData: []*ecr.AuthorizationData{data}, | ||
} | ||
|
||
return output, nil //p.svc.GetAuthorizationToken(input) | ||
} | ||
|
||
func TestEcrProvide(t *testing.T) { | ||
registry := "123456789012.dkr.ecr.lala-land-1.amazonaws.com" | ||
otherRegistries := []string{"private.registry.com", | ||
"gcr.io", | ||
} | ||
image := "foo/bar" | ||
|
||
provider := &ecrProvider{ | ||
getter: &testTokenGetter{ | ||
user: user, | ||
password: password, | ||
endpoint: registry}, | ||
} | ||
|
||
keyring := &credentialprovider.BasicDockerKeyring{} | ||
keyring.Add(provider.Provide()) | ||
|
||
// Verify that we get the expected username/password combo for | ||
// an ECR image name. | ||
fullImage := path.Join(registry, image) | ||
creds, ok := keyring.Lookup(fullImage) | ||
if !ok { | ||
t.Errorf("Didn't find expected URL: %s", fullImage) | ||
return | ||
} | ||
if len(creds) > 1 { | ||
t.Errorf("Got more hits than expected: %s", creds) | ||
} | ||
val := creds[0] | ||
|
||
if user != val.Username { | ||
t.Errorf("Unexpected username value, want: _token, got: %s", val.Username) | ||
} | ||
if password != val.Password { | ||
t.Errorf("Unexpected password value, want: %s, got: %s", password, val.Password) | ||
} | ||
if email != val.Email { | ||
t.Errorf("Unexpected email value, want: %s, got: %s", email, val.Email) | ||
} | ||
|
||
// Verify that we get an error for other images. | ||
for _, otherRegistry := range otherRegistries { | ||
fullImage = path.Join(otherRegistry, image) | ||
creds, ok = keyring.Lookup(fullImage) | ||
if ok { | ||
t.Errorf("Unexpectedly found image: %s", fullImage) | ||
return | ||
} | ||
} | ||
} |