Skip to content
Open
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
33 changes: 32 additions & 1 deletion cyclops-ctrl/pkg/auth/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ var _ = Describe("Templates resolver", func() {
},
},
},
{
Spec: v1alpha1.TemplateAuthRuleSpec{
Repo: "oci://registry.example.com/my-org",
Username: apiv1.SecretKeySelector{
LocalObjectReference: apiv1.LocalObjectReference{Name: "oci-secret-name"},
Key: "username",
},
Password: apiv1.SecretKeySelector{
LocalObjectReference: apiv1.LocalObjectReference{Name: "oci-secret-name"},
Key: "token",
},
},
},
}

testCases := []testCase{
Expand Down Expand Up @@ -154,7 +167,7 @@ var _ = Describe("Templates resolver", func() {
},
},
{
description: "fetches no matching template auth rules",
description: "fetches matching template auth rule",
in: caseInput{
repo: "https://github.com/my-org/my-team",
mockCalls: func() {
Expand All @@ -171,6 +184,24 @@ var _ = Describe("Templates resolver", func() {
returnsError: false,
},
},
{
description: "fetches matching OCI template auth rule",
in: caseInput{
repo: "oci://registry.example.com/my-org",
mockCalls: func() {
k8sClient.On("ListTemplateAuthRules").Return(tars, nil)
k8sClient.On("GetTemplateAuthRuleSecret", "oci-secret-name", "username").Return("my-oci-username", nil)
k8sClient.On("GetTemplateAuthRuleSecret", "oci-secret-name", "token").Return("my-oci-token", nil)
},
},
out: caseOutput{
credentials: &Credentials{
Username: "my-oci-username",
Password: "my-oci-token",
},
returnsError: false,
},
},
}

for _, t := range testCases {
Expand Down
115 changes: 77 additions & 38 deletions cyclops-ctrl/pkg/template/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ import (

cyclopsv1alpha1 "github.com/cyclops-ui/cyclops/cyclops-ctrl/api/v1alpha1"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/internal/models"
"github.com/cyclops-ui/cyclops/cyclops-ctrl/pkg/auth"
)

func (r Repo) LoadOCIHelmChart(repo, chart, version, resolvedVersion string) (*models.Template, error) {
var err error
strictVersion := version
creds, err := r.credResolver.RepoAuthCredentials(repo)
if err != nil {
return nil, err
}

if len(resolvedVersion) > 0 {
strictVersion = resolvedVersion
} else if !isValidVersion(version) {
strictVersion, err = getOCIStrictVersion(repo, chart, version)
strictVersion, err = getOCIStrictVersion(repo, chart, version, creds)
if err != nil {
return nil, err
}
Expand All @@ -33,7 +38,7 @@ func (r Repo) LoadOCIHelmChart(repo, chart, version, resolvedVersion string) (*m
}

var tgzData []byte
tgzData, err = loadOCIHelmChartBytes(repo, chart, version)
tgzData, err = loadOCIHelmChartBytes(repo, chart, version, creds)
if err != nil {
return nil, err
}
Expand All @@ -59,8 +64,13 @@ func (r Repo) LoadOCIHelmChart(repo, chart, version, resolvedVersion string) (*m
func (r Repo) LoadOCIHelmChartInitialValues(repo, chart, version string) (map[string]interface{}, error) {
var err error
strictVersion := version
creds, err := r.credResolver.RepoAuthCredentials(repo)
if err != nil {
return nil, err
}

if !isValidVersion(version) {
strictVersion, err = getOCIStrictVersion(repo, chart, version)
strictVersion, err = getOCIStrictVersion(repo, chart, version, creds)
if err != nil {
return nil, err
}
Expand All @@ -71,7 +81,7 @@ func (r Repo) LoadOCIHelmChartInitialValues(repo, chart, version string) (map[st
return cached, nil
}

tgzData, err := loadOCIHelmChartBytes(repo, chart, version)
tgzData, err := loadOCIHelmChartBytes(repo, chart, version, creds)
if err != nil {
return nil, err
}
Expand All @@ -91,34 +101,34 @@ func (r Repo) LoadOCIHelmChartInitialValues(repo, chart, version string) (map[st
return initial, nil
}

func loadOCIHelmChartBytes(repo, chart, version string) ([]byte, error) {
func loadOCIHelmChartBytes(repo, chart, version string, creds *auth.Credentials) ([]byte, error) {
var err error
if !isValidVersion(version) {
version, err = getOCIStrictVersion(repo, chart, version)
version, err = getOCIStrictVersion(repo, chart, version, creds)
if err != nil {
return nil, err
}
}

token, err := authorizeOCI(repo, chart, version)
token, err := authorizeOCI(repo, chart, version, creds)
if err != nil {
return nil, err
}

digest, err := fetchDigest(repo, chart, version, token)
digest, err := fetchDigest(repo, chart, version, token, creds)
if err != nil {
return nil, err
}

contentDigest, err := fetchContentDigest(repo, chart, digest, token)
contentDigest, err := fetchContentDigest(repo, chart, digest, token, creds)
if err != nil {
return nil, err
}

return loadOCITar(repo, chart, contentDigest, token)
return loadOCITar(repo, chart, contentDigest, token, creds)
}

func loadOCITar(repo, chart, digest, token string) ([]byte, error) {
func loadOCITar(repo, chart, digest, token string, creds *auth.Credentials) ([]byte, error) {
bURL, err := blobURL(repo, chart, digest)
if err != nil {
return nil, err
Expand All @@ -131,9 +141,7 @@ func loadOCITar(repo, chart, digest, token string) ([]byte, error) {

req.Header.Set("User-Agent", "Helm/3.13.3")
req.Header.Set("Accept", "application/vnd.cncf.helm.config.v1+json, */*")
if len(token) != 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
}
setOCIRegistryAuth(req, token, creds)

client := &http.Client{}
resp, err := client.Do(req)
Expand All @@ -145,7 +153,7 @@ func loadOCITar(repo, chart, digest, token string) ([]byte, error) {
return ioutil.ReadAll(resp.Body)
}

func fetchContentDigest(repo, chart, digest, token string) (string, error) {
func fetchContentDigest(repo, chart, digest, token string, creds *auth.Credentials) (string, error) {
dURL, err := contentDigestURL(repo, chart, digest)
if err != nil {
return "", err
Expand All @@ -158,9 +166,7 @@ func fetchContentDigest(repo, chart, digest, token string) (string, error) {

req.Header.Set("User-Agent", "Helm/3.13.3")
req.Header.Set("Accept", "application/vnd.oci.image.manifest.v1+json, */*")
if len(token) != 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
}
setOCIRegistryAuth(req, token, creds)

client := &http.Client{}
resp, err := client.Do(req)
Expand Down Expand Up @@ -198,7 +204,7 @@ func fetchContentDigest(repo, chart, digest, token string) (string, error) {
return ct.Layers[0].Digest, nil
}

func fetchDigest(repo, chart, version, token string) (string, error) {
func fetchDigest(repo, chart, version, token string, creds *auth.Credentials) (string, error) {
dURL, err := digestURL(repo, chart, version)
if err != nil {
return "", err
Expand All @@ -211,9 +217,7 @@ func fetchDigest(repo, chart, version, token string) (string, error) {

req.Header.Set("User-Agent", "Helm/3.13.3")
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, */*")
if len(token) != 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
}
setOCIRegistryAuth(req, token, creds)

client := &http.Client{}
resp, err := client.Do(req)
Expand All @@ -225,8 +229,8 @@ func fetchDigest(repo, chart, version, token string) (string, error) {
return resp.Header.Get("docker-content-digest"), nil
}

func getOCIStrictVersion(repo, chart, version string) (string, error) {
allTags, err := GetOCIChartTags(repo, chart)
func getOCIStrictVersion(repo, chart, version string, creds *auth.Credentials) (string, error) {
allTags, err := getOCIChartTags(repo, chart, creds)
if err != nil {
return "", err
}
Expand All @@ -235,7 +239,11 @@ func getOCIStrictVersion(repo, chart, version string) (string, error) {
}

func GetOCIChartTags(repo, chart string) ([]string, error) {
token, err := authorizeOCITags(repo, chart)
return getOCIChartTags(repo, chart, nil)
}

func getOCIChartTags(repo, chart string, creds *auth.Credentials) ([]string, error) {
token, err := authorizeOCITags(repo, chart, creds)
if err != nil {
return nil, err
}
Expand All @@ -254,9 +262,7 @@ func GetOCIChartTags(repo, chart string) ([]string, error) {
}

req.Header.Set("User-Agent", "Helm/3.13.3")
if len(token) != 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
}
setOCIRegistryAuth(req, token, creds)

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -303,7 +309,7 @@ func GetOCIChartTags(repo, chart string) ([]string, error) {
return allTags, err
}

func authorizeOCI(repo, chart, version string) (string, error) {
func authorizeOCI(repo, chart, version string, creds *auth.Credentials) (string, error) {
// region head
dURL, err := digestURL(repo, chart, version)
if err != nil {
Expand All @@ -319,6 +325,7 @@ func authorizeOCI(repo, chart, version string) (string, error) {

req.Header.Set("User-Agent", "Helm/3.13.3")
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, */*")
setOCIRegistryAuth(req, "", creds)

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -355,6 +362,7 @@ func authorizeOCI(repo, chart, version string) (string, error) {

req.Header.Set("User-Agent", "Helm/3.13.3")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
setOCITokenAuth(req, creds)

resp, err = client.Do(req)
if err != nil {
Expand All @@ -367,20 +375,17 @@ func authorizeOCI(repo, chart, version string) (string, error) {
return "", err
}

var ar struct {
Token string `json:"token"`
}

if err := json.Unmarshal(responseBody, &ar); err != nil {
token, err := parseOCITokenResponse(responseBody)
if err != nil {
return "", err
}

return ar.Token, nil
return token, nil

// endregion
}

func authorizeOCITags(repo, chart string) (string, error) {
func authorizeOCITags(repo, chart string, creds *auth.Credentials) (string, error) {
// region head
tURL, err := tagsURL(repo, chart)
if err != nil {
Expand All @@ -393,6 +398,8 @@ func authorizeOCITags(repo, chart string) (string, error) {
if err != nil {
return "", err
}
req.Header.Set("User-Agent", "Helm/3.13.3")
setOCIRegistryAuth(req, "", creds)

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -428,6 +435,7 @@ func authorizeOCITags(repo, chart string) (string, error) {

req.Header.Set("User-Agent", "Helm/3.13.3")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
setOCITokenAuth(req, creds)

resp, err = client.Do(req)
if err != nil {
Expand All @@ -440,17 +448,48 @@ func authorizeOCITags(repo, chart string) (string, error) {
return "", err
}

token, err := parseOCITokenResponse(responseBody)
if err != nil {
return "", err
}

return token, nil

// endregion
}

func parseOCITokenResponse(responseBody []byte) (string, error) {
var ar struct {
Token string `json:"token"`
Token string `json:"token"`
AccessToken string `json:"access_token"`
}

if err := json.Unmarshal(responseBody, &ar); err != nil {
return "", err
}

if len(ar.Token) == 0 {
return ar.AccessToken, nil
}

return ar.Token, nil
}

// endregion
func setOCIRegistryAuth(req *http.Request, token string, creds *auth.Credentials) {
if len(token) != 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", token))
return
}

if creds != nil {
req.SetBasicAuth(creds.Username, creds.Password)
}
}

func setOCITokenAuth(req *http.Request, creds *auth.Credentials) {
if creds != nil {
req.SetBasicAuth(creds.Username, creds.Password)
}
}

func parseAuthenticateHeader(header string) (realm, service, scope string) {
Expand Down
Loading