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

Gardener config credential repository #55

Merged
merged 32 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
786edce
add dev docs diagrams
jschicktanz Jul 8, 2022
0cb6e36
implement first version of secret server credentials repo
jschicktanz Jul 13, 2022
f710aee
fix typo
jschicktanz Jul 13, 2022
302ffbb
add pointer receivers to credential repos
jschicktanz Jul 13, 2022
8ab9366
v2 implementation
jschicktanz Jul 29, 2022
938cfb6
finish first working version
jschicktanz Aug 1, 2022
4e86cc0
rework
jschicktanz Aug 2, 2022
dcae60d
simplify code
jschicktanz Aug 2, 2022
734c8c5
rework
jschicktanz Aug 3, 2022
bd82787
only add scheme and port to consumer identity if not empty
jschicktanz Aug 8, 2022
d438e8e
refactoring
jschicktanz Aug 9, 2022
314dcd9
fix import cycle
jschicktanz Aug 10, 2022
d42f6ad
several bugfixes
jschicktanz Aug 10, 2022
b41d116
bugfixes
jschicktanz Aug 11, 2022
19afc32
Merge branch 'main' into js-work
jschicktanz Aug 11, 2022
651e0b1
fix compile errors
jschicktanz Aug 11, 2022
58870c0
run go mod tidy
jschicktanz Aug 11, 2022
71649a2
review feedback
jschicktanz Aug 12, 2022
df967b8
Review feedback
jschicktanz Aug 12, 2022
7dba5bc
fix indent
jschicktanz Aug 12, 2022
71baf49
trim leading slashes when creating consumer identities
jschicktanz Aug 12, 2022
3c08c1e
review feedback
jschicktanz Aug 12, 2022
1521565
read encryption key from context
jschicktanz Aug 18, 2022
93d2bc8
add missing code
jschicktanz Aug 18, 2022
94d51b9
Merge branch 'main' into js-work
jschicktanz Aug 18, 2022
5c916d8
make format
jschicktanz Aug 18, 2022
50a0c94
review feedback
jschicktanz Aug 22, 2022
52eaa24
small refactoring
jschicktanz Aug 23, 2022
016112e
Merge branch 'main' into js-work
Skarlso Aug 26, 2022
b8b933a
review feedback
jschicktanz Aug 31, 2022
9e16811
allow retry of read operation if secret server is unavailable
jschicktanz Aug 31, 2022
c877f6e
review feedback
jschicktanz Sep 2, 2022
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
Binary file added docs/development/context-structure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions pkg/contexts/credentials/core/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ const (
ATTR_TOKEN = "token"
ATTR_AWS_ACCESS_KEY_ID = "awsAccessKeyID"
ATTR_AWS_SECRET_ACCESS_KEY = "awsSecretAccessKey"
ATTR_KEY = "key"
)
13 changes: 10 additions & 3 deletions pkg/contexts/credentials/core/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,24 @@ func (i ConsumerIdentity) Match(obj map[string]string) bool {
}

// Copy copies identity
func (l ConsumerIdentity) Copy() ConsumerIdentity {
if l == nil {
func (i ConsumerIdentity) Copy() ConsumerIdentity {
if i == nil {
return nil
}
n := ConsumerIdentity{}
for k, v := range l {
for k, v := range i {
n[k] = v
}
return n
}

// SetNonEmptyValue sets a key-value pair only if the value is not empty
func (i ConsumerIdentity) SetNonEmptyValue(name, value string) {
if value != "" {
i[name] = value
}
}

////////////////////////////////////////////////////////////////////////////////

type IdentityMatcherInfo struct {
Expand Down
1 change: 1 addition & 0 deletions pkg/contexts/credentials/cpi/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ const (
ATTR_SERVER_ADDRESS = core.ATTR_SERVER_ADDRESS
ATTR_IDENTITY_TOKEN = core.ATTR_IDENTITY_TOKEN
ATTR_REGISTRY_TOKEN = core.ATTR_REGISTRY_TOKEN
ATTR_KEY = core.ATTR_KEY
)
9 changes: 9 additions & 0 deletions pkg/contexts/credentials/identity/hostpath/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const ID_PORT = "port"
// ID_PATHPREFIX is the path prefix below the host.
const ID_PATHPREFIX = "pathprefix"

// ID_SCHEME is the scheme prefix.
const ID_SCHEME = "scheme"

func init() {
cpi.RegisterStandardIdentityMatcher(IDENTITY_TYPE, IdentityMatcher(""), `Host and path based credential matcher

Expand Down Expand Up @@ -60,6 +63,12 @@ func IdentityMatcher(identityType string) cpi.IdentityMatcher {
}
}

if pattern[ID_SCHEME] != "" {
mandelsoft marked this conversation as resolved.
Show resolved Hide resolved
if id[ID_SCHEME] != "" && id[ID_SCHEME] != pattern[ID_SCHEME] {
return false
}
}

if pattern[ID_PATHPREFIX] != "" {
if id[ID_PATHPREFIX] != "" {
if len(id[ID_PATHPREFIX]) > len(pattern[ID_PATHPREFIX]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (r *Repository) LookupCredentials(name string) (cpi.Credentials, error) {
return r.Credentials, nil
}

func (r Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) {
func (r *Repository) WriteCredentials(name string, creds cpi.Credentials) (cpi.Credentials, error) {
return nil, errors.ErrNotSupported(cpi.KIND_CREDENTIALS, "write", "constant credential")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func init() {
cpi.RegisterRepositoryType(TypeV1, cpi.NewRepositoryType(TypeV1, &RepositorySpec{}))
}

// RepositorySpec describes a cocker config based credential repository interface.
// RepositorySpec describes a docker config based credential repository interface.
type RepositorySpec struct {
runtime.ObjectVersionedType `json:",inline"`
DockerConfigFile string `json:"dockerConfigFile"`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Gardener Config Credential Repository

The gardener config credential repository implements the retrieval of secrets in a data format specified by the gardener concourse utils (https://github.com/gardener/cc-utils/tree/master/model). It supports either handing in the data via a local json file or retrieve it from a secret server (as defined in https://github.com/gardener/cc-utils/blob/master/ccc/secrets_server.py#L29).
36 changes: 36 additions & 0 deletions pkg/contexts/credentials/repositories/gardenerconfig/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gardenerconfig

import (
"fmt"
"sync"

"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi"
gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi"
"github.com/open-component-model/ocm/pkg/contexts/datacontext"
)

const ATTR_REPOS = "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig"

type Repositories struct {
lock sync.Mutex
repos map[string]*Repository
}

func newRepositories(datacontext.Context) interface{} {
return &Repositories{
repos: map[string]*Repository{},
}
}

func (r *Repositories) GetRepository(ctx cpi.Context, url string, configType gardenercfgcpi.ConfigType, cipher Cipher, key []byte, propagateConsumerIdentity bool) (*Repository, error) {
r.lock.Lock()
defer r.lock.Unlock()
if _, ok := r.repos[url]; !ok {
repo, err := NewRepository(ctx, url, configType, cipher, key, propagateConsumerIdentity)
if err != nil {
return nil, fmt.Errorf("unable to create repository: %w", err)
}
r.repos[url] = repo
}
return r.repos[url], nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cpi

import (
"io"
"sync"

"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi"
)

type ConfigType string

const (
ContainerRegistry ConfigType = "container_registry"
)

type Credential interface {
Name() string
ConsumerIdentity() cpi.ConsumerIdentity
Properties() cpi.Credentials
}

type Handler interface {
ConfigType() ConfigType
ParseConfig(io.Reader) ([]Credential, error)
}

var handlers = map[ConfigType]Handler{}
var lock sync.RWMutex

func RegisterHandler(h Handler) {
lock.Lock()
defer lock.Unlock()
handlers[h.ConfigType()] = h
}

func GetHandler(configType ConfigType) Handler {
lock.RLock()
defer lock.RUnlock()
return handlers[configType]
}

func GetHandlers() map[ConfigType]Handler {
lock.RLock()
defer lock.RUnlock()

m := map[ConfigType]Handler{}
for k, v := range handlers {
m[k] = v
}
return m
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package gardenerconfig

import (
"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi"
)

type CredentialGetter struct {
getCredentials func() (cpi.Credentials, error)
}
jschicktanz marked this conversation as resolved.
Show resolved Hide resolved

var _ cpi.CredentialsSource = CredentialGetter{}

func (c CredentialGetter) Credentials(ctx cpi.Context, cs ...cpi.CredentialsSource) (cpi.Credentials, error) {
return c.getCredentials()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package container_registry

import (
"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi"
gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi"
)

type credentials struct {
name string
consumerIdentity cpi.ConsumerIdentity
properties cpi.Credentials
}

func (c credentials) Name() string {
return c.name
}

func (c credentials) ConsumerIdentity() cpi.ConsumerIdentity {
return c.consumerIdentity
}

func (c credentials) Properties() cpi.Credentials {
return c.properties
}

var _ gardenercfgcpi.Credential = credentials{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package container_registry

import (
"encoding/json"
"fmt"
"io"
"strings"

"github.com/open-component-model/ocm/pkg/common"
"github.com/open-component-model/ocm/pkg/contexts/credentials/cpi"
"github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath"
gardenercfgcpi "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/cpi"
"github.com/open-component-model/ocm/pkg/contexts/oci/identity"
"github.com/open-component-model/ocm/pkg/utils"
)

func init() {
gardenercfgcpi.RegisterHandler(Handler{})
}

// config is the struct that describes the gardener config data structure
type config struct {
ContainerRegistry map[string]*containerRegistryCredentials `json:"container_registry"`
}

// containerRegistryCredentials describes the container registry credentials struct as defined by the gardener config.
type containerRegistryCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
Privileges string `json:"privileges"`
Host string `json:"host,omitempty"`
ImageReferencePrefixes []string `json:"image_reference_prefixes,omitempty"`
Skarlso marked this conversation as resolved.
Show resolved Hide resolved
}

type Handler struct{}

func (h Handler) ConfigType() gardenercfgcpi.ConfigType {
return gardenercfgcpi.ContainerRegistry
}

func (h Handler) ParseConfig(configReader io.Reader) ([]gardenercfgcpi.Credential, error) {
config := &config{}
if err := json.NewDecoder(configReader).Decode(&config); err != nil {
return nil, fmt.Errorf("unable to unmarshal config: %w", err)
}

creds := []gardenercfgcpi.Credential{}
for credentialName, credential := range config.ContainerRegistry {
var (
scheme string
port string
)
if credential.Host != "" {
parsedHost, err := utils.ParseURL(credential.Host)
if err != nil {
return nil, fmt.Errorf("unable to parse host: %w", err)
}
scheme = parsedHost.Scheme
port = parsedHost.Port()
}

for _, imgRef := range credential.ImageReferencePrefixes {
parsedImgPrefix, err := utils.ParseURL(imgRef)
if err != nil {
return nil, fmt.Errorf("unable to parse image prefix: %w", err)
}
if parsedImgPrefix.Host == "index.docker.io" {
parsedImgPrefix.Host = "docker.io"
}

consumerIdentity := cpi.ConsumerIdentity{
cpi.CONSUMER_ATTR_TYPE: identity.CONSUMER_TYPE,
hostpath.ID_HOSTNAME: parsedImgPrefix.Host,
hostpath.ID_PATHPREFIX: strings.Trim(parsedImgPrefix.Path, "/"),
}
consumerIdentity.SetNonEmptyValue(hostpath.ID_SCHEME, scheme)
consumerIdentity.SetNonEmptyValue(hostpath.ID_PORT, port)

c := credentials{
name: credentialName,
consumerIdentity: consumerIdentity,
properties: newCredentialsFromContainerRegistryCredentials(credential),
}

creds = append(creds, c)
}
}

return creds, nil
}

func newCredentialsFromContainerRegistryCredentials(auth *containerRegistryCredentials) cpi.Credentials {
props := common.Properties{
cpi.ATTR_USERNAME: auth.Username,
cpi.ATTR_PASSWORD: auth.Password,
}
props.SetNonEmptyValue(cpi.ATTR_SERVER_ADDRESS, auth.Host)
return cpi.NewCredentials(props)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gardenerconfig

import (
_ "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/gardenerconfig/handler/container_registry"
)
Loading