Skip to content

Commit

Permalink
add turnpike content guard
Browse files Browse the repository at this point in the history
Signed-off-by: Jonathan Holloway <[email protected]>
  • Loading branch information
loadtheaccumulator committed Dec 11, 2024
1 parent 3d1b4bd commit 97ee9c9
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 40 deletions.
9 changes: 7 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type EdgeConfig struct {
SubscriptionServerURL string `json:"subscription_server_url"`
SubscriptionBaseUrl string `json:"subscription_base_url"`
PulpURL string `json:"pulp_url,omitempty"`
PulpContentURL string `json:"pulp_content_url,omitempty"`
PulpUsername string `json:"pulp_username,omitempty"`
PulpPassword string `json:"pulp_password,omitempty"`
PulpContentUsername string `json:"pulp_content_username,omitempty"`
Expand Down Expand Up @@ -130,6 +131,7 @@ type Pulp struct {
URL string
Username string
Password string
ContentURL string `mapstructure:"content_url,omitempty"`
ContentUsername string `mapstructure:"content_username,omitempty"`
ContentPassword string `mapstructure:"content_password,omitempty"`
IdentityName string `mapstructure:"identity_name,omitempty"`
Expand Down Expand Up @@ -175,7 +177,8 @@ func readPulpConfig() (Pulp, error) {
options.SetDefault("url", "http://pulp-service:8080")
options.SetDefault("username", "edge-api-dev")
options.SetDefault("password", "")
options.SetDefault("content_username", "edge-content-dev")
options.SetDefault("content_url", "http://pulp-service:8080")
options.SetDefault("content_username", "")
options.SetDefault("content_password", "")
options.SetDefault("guard_subject_dn", "")
options.SetDefault("identity_name", "edge-api-dev")
Expand Down Expand Up @@ -331,6 +334,7 @@ func CreateEdgeAPIConfig() (*EdgeConfig, error) {
SubscriptionServerURL: options.GetString("SUBSCRIPTION_SERVER_URL"),
SubscriptionBaseUrl: options.GetString("SUBSCRIPTION_BASE_URL"),
PulpURL: pulpConfig.URL, // these pulp entries are temporary for backward compatibility
PulpContentURL: pulpConfig.ContentURL,
PulpUsername: pulpConfig.Username,
PulpPassword: pulpConfig.Password,
PulpContentUsername: pulpConfig.ContentUsername,
Expand Down Expand Up @@ -500,7 +504,7 @@ func Get() *EdgeConfig {
return config
}

func cleanup() {
func Cleanup() {
configMu.Lock()
defer configMu.Unlock()

Expand Down Expand Up @@ -562,6 +566,7 @@ func LogConfigAtStartup(cfg *EdgeConfig) {
"RepoFileUploadDelay": cfg.RepoFileUploadDelay,
"UploadWorkers": cfg.UploadWorkers,
"PulpURL": cfg.PulpURL,
"PulpContentURL": cfg.PulpContentURL,
"PulpGuardSubjectDN": cfg.PulpGuardSubjectDN,
}

Expand Down
6 changes: 3 additions & 3 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestKafkaBroker(t *testing.T) {

}(originalConfig, originalClowderEnvConfig, originalClowderLoadedConfig, originalClowderObjectBuckets, originalEDGETarBallsBucket)

defer cleanup()
defer Cleanup()

err := os.Setenv("ACG_CONFIG", "need some value only, as the config path is not needed here")
assert.NoError(t, err)
Expand Down Expand Up @@ -170,9 +170,9 @@ func TestKafkaBroker(t *testing.T) {
clowder.LoadedConfig = testCase.clowderConfig
clowder.KafkaServers = testCase.KafkaServers
// init the configuration
cleanup()
Cleanup()
Init()
defer cleanup()
defer Cleanup()
assert.Equal(t, config.KafkaBroker, testCase.ExpectedKafkaBroker)
assert.Equal(t, config.KafkaServers, testCase.ExpectedKafkaServers)
if testCase.ExpectedKafkaBroker != nil {
Expand Down
5 changes: 5 additions & 0 deletions deploy/clowdapp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ objects:
secretKeyRef:
name: edge-pulp-password
key: key
- name : PULP_CONTENT_URL
value: ${PULP_CONTENT_URL}
- name : PULP_CONTENT_USERNAME
value: ${PULP_CONTENT_USERNAME}
- name : PULP_CONTENT_PASSWORD
Expand Down Expand Up @@ -555,6 +557,9 @@ parameters:
value: "pulp-user"
- name: PULP_PASSWORD
description: Password for Pulp API authentication
- name: PULP_CONTENT_URL
description: Pulp service content URL
value: "http://pulp-service:8080"
- name: PULP_CONTENT_USERNAME
description: Username for Pulp Content API authentication
value: "pulp-content-user"
Expand Down
22 changes: 19 additions & 3 deletions pkg/clients/imagebuilder/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type ImageRequest struct {

// ComposeRequest is the request to Compose one or more Images
type ComposeRequest struct {
Name string `json:"image_name"`
Customizations *Customizations `json:"customizations"`
Distribution string `json:"distribution"`
ImageRequests []ImageRequest `json:"image_requests"`
Expand Down Expand Up @@ -224,12 +225,15 @@ func (c *Client) compose(composeReq *ComposeRequest) (*ComposeResult, error) {
return nil, err
}
cfg := config.Get()
url := fmt.Sprintf("%s/api/image-builder/v1/compose", cfg.ImageBuilderConfig.URL)
reqURL := fmt.Sprintf("%s/api/image-builder/v1/compose", cfg.ImageBuilderConfig.URL)

parsedURL, _ := url.Parse(reqURL)
c.log.WithFields(log.Fields{
"url": url,
"url": parsedURL.Redacted(),
"payload": payloadBuf.String(),
}).Debug("Image Builder Compose Request Started")
req, _ := http.NewRequest("POST", url, payloadBuf)

req, _ := http.NewRequest("POST", reqURL, payloadBuf)
for key, value := range clients.GetOutgoingHeaders(c.ctx) {
req.Header.Add(key, value)
}
Expand Down Expand Up @@ -267,11 +271,14 @@ func (c *Client) compose(composeReq *ComposeRequest) (*ComposeResult, error) {

// ComposeCommit composes a Commit on ImageBuilder
func (c *Client) ComposeCommit(image *models.Image) (*models.Image, error) {
c.log.Debug("COMPOSING COMMIT")

payloadRepos, err := c.GetImageThirdPartyRepos(image)
if err != nil {
return nil, errors.New("error getting information on third Party repository")
}
req := &ComposeRequest{
Name: image.Name,
Customizations: &Customizations{
Packages: image.GetALLPackagesList(),
PayloadRepositories: &payloadRepos,
Expand Down Expand Up @@ -303,6 +310,7 @@ func (c *Client) ComposeCommit(image *models.Image) (*models.Image, error) {
}

}

if image.Commit.OSTreeRef != "" {
if req.ImageRequests[0].Ostree == nil {
req.ImageRequests[0].Ostree = &OSTree{}
Expand Down Expand Up @@ -338,6 +346,8 @@ func (c *Client) ComposeCommit(image *models.Image) (*models.Image, error) {

// ComposeInstaller composes an Installer on ImageBuilder
func (c *Client) ComposeInstaller(image *models.Image) (*models.Image, error) {
c.log.Debug("COMPOSING INSTALLER")

pkgs := make([]string, 0)
var repoURL string
var rhsm bool
Expand All @@ -349,6 +359,12 @@ func (c *Client) ComposeInstaller(image *models.Image) (*models.Image, error) {
rhsm = false
}

if feature.PulpIntegration.IsEnabled() && image.Commit.Repo.PulpURL != "" {
repoURL = image.Commit.Repo.PulpURL
parsedURL, _ := url.Parse(repoURL)
c.log.WithField("redacted_url", parsedURL.Redacted()).Debug("Using Pulp repo URL for ISO installer request")
}

users := make([]User, 0)
if image.Installer != nil && image.Installer.Username != "" && image.Installer.SSHKey != "" {
users = append(users, User{Name: image.Installer.Username,
Expand Down
65 changes: 59 additions & 6 deletions pkg/clients/pulp/guards_composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"errors"
"fmt"
"slices"

"github.com/google/uuid"
"github.com/redhatinsights/edge-api/config"
"github.com/redhatinsights/edge-api/pkg/ptr"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -65,12 +67,44 @@ func (ps *PulpService) CompositeGuardReadByOrgID(ctx context.Context, orgID stri
return ps.CompositeGuardRead(ctx, id)
}

func contentGuardHrefsAreEqual(cghrefa, cghrefb []string) bool {
// compare how many hrefs are in the content guard versus expected
logrus.Debug("Comparing Content Guard hrefs")
if len(cghrefa) != len(cghrefb) {
logrus.WithFields(logrus.Fields{
"href_count_1": len(cghrefa),
"href_count_2": len(cghrefb),
}).Warning("Content Guards do not have the same number of hrefs")

return false
}

logrus.WithFields(logrus.Fields{
"href_count_1": len(cghrefa),
"href_count_2": len(cghrefb),
}).Debug("Content Guards have the same number of hrefs")

// compare content guard hrefs (actual vs. expected)
for i := range cghrefa {
if !slices.Contains(cghrefb, cghrefa[i]) {
logrus.WithField("href_mismatch", cghrefa[i]).Warning("Content Guard href mismatch")

return false
}
}

logrus.Debug("Content Guard has the expected hrefs")

return true
}

// CompositeGuardEnsure ensures that the composite guard is created and returns it. The method is idempotent.
func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID, headerHref, rbacHref string) (*CompositeContentGuardResponse, error) {
func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID string, pulpGuardHrefs []string) (*CompositeContentGuardResponse, error) {
cg, err := ps.CompositeGuardReadByOrgID(ctx, compositeGuardName(orgID))
// nolint: gocritic
if errors.Is(err, ErrRecordNotFound) {
cg, err = ps.CompositeGuardCreate(ctx, orgID, headerHref, rbacHref)
logrus.Warning("No composite guard found. Creating one.")
cg, err = ps.CompositeGuardCreate(ctx, orgID, pulpGuardHrefs...)
if err != nil {
return nil, err
}
Expand All @@ -83,14 +117,15 @@ func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID, headerHr
}

gs := ptr.FromOrEmpty(cg.Guards)
if !(len(gs) == 2 && (gs[0] != headerHref && gs[1] != rbacHref) || (gs[1] != headerHref && gs[0] != rbacHref)) {
if !contentGuardHrefsAreEqual(gs, pulpGuardHrefs) {
logrus.WithContext(ctx).Warnf("unexpected Composite Content Guard: %v, deleting it", gs)
err = ps.CompositeGuardDelete(ctx, ScanUUID(cg.PulpHref))
if err != nil {
return nil, err
}

cg, err = ps.CompositeGuardCreate(ctx, orgID, headerHref, rbacHref)
logrus.Warning("Matching composite guard not found. Creating one.")
cg, err = ps.CompositeGuardCreate(ctx, orgID, pulpGuardHrefs...)
if err != nil {
return nil, err
}
Expand All @@ -102,17 +137,35 @@ func (ps *PulpService) CompositeGuardEnsure(ctx context.Context, orgID, headerHr
// that the composite guard is not created or the guards are not the same as the ones provided, it will delete it
// and recreate it. This method is idempotent and will not create the guards if they already exist.
func (ps *PulpService) ContentGuardEnsure(ctx context.Context, orgID string) (*CompositeContentGuardResponse, error) {
var contentGuardHrefs []string
cfg := config.Get()

logrus.WithContext(ctx).Debug("Creating header content guard")
hcg, err := ps.HeaderGuardEnsure(ctx, orgID)
if err != nil {
return nil, err
}
contentGuardHrefs = append(contentGuardHrefs, *hcg.PulpHref)

rcg, err := ps.RbacGuardEnsure(ctx)
// Turnpike Content Guard is primarily for Image Builder to Pulp calls using the URL configured in PULP_CONTENT_URL
logrus.WithContext(ctx).Debug("Creating turnpike content guard")
tpcg, err := ps.TurnpikeGuardEnsure(ctx)
if err != nil {
return nil, err
}
contentGuardHrefs = append(contentGuardHrefs, *tpcg.PulpHref)

// to create an RBAC Content Guard, add PULP_CONTENT_USERNAME and PULP_CONTENT_PASSWORD to environment
if cfg.Pulp.ContentUsername != "" {
logrus.WithContext(ctx).Debug("Creating RBAC content guard")
rcg, err := ps.RbacGuardEnsure(ctx)
if err != nil {
return nil, err
}
contentGuardHrefs = append(contentGuardHrefs, *rcg.PulpHref)
}

ccg, err := ps.CompositeGuardEnsure(ctx, orgID, *hcg.PulpHref, *rcg.PulpHref)
ccg, err := ps.CompositeGuardEnsure(ctx, orgID, contentGuardHrefs)
if err != nil {
return nil, err
}
Expand Down
91 changes: 91 additions & 0 deletions pkg/clients/pulp/guards_composite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package pulp

import (
"fmt"
"testing"

"github.com/bxcodec/faker/v3"
"github.com/magiconair/properties/assert"
)

func TestContentGuardHrefsAreEqual(t *testing.T) {
var hrefTemplate = "/api/pulp/em%sd/api/v3/contentguards/core/%s/%s/"
var orgID = faker.UUIDDigit()

var href1 = fmt.Sprintf(hrefTemplate, orgID, "header", faker.UUIDHyphenated()) // id
var href2 = fmt.Sprintf(hrefTemplate, orgID, "rbac", faker.UUIDHyphenated()) // rbac
var href3 = fmt.Sprintf(hrefTemplate, orgID, "header", faker.UUIDHyphenated()) // turnpike
var href4 = fmt.Sprintf(hrefTemplate, orgID, "header", faker.UUIDHyphenated()) // different

var baseSlice = []string{href1, href2, href3}
var sameSizeDiffOrder = []string{href3, href2, href1}
var sameSizeSameOrder = []string{href1, href2, href3}
var sameSizeDiffContent = []string{href1, href2, href4}
var diffSmaller = []string{href1, href2}
var diffLarger = []string{href1, href2, href3, href4}
var sameSizeEmptyString = []string{href1, href2, ""}
var sameSizeEmptyStringDiffOrder = []string{"", href2, href1}
var sameSizeAllEmptyStrings = []string{"", "", ""}
var diffSizeSmallerAllEmptyStrings = []string{"", ""}
var diffSizeLargerAllEmptyStrings = []string{"", "", "", ""}

t.Run("sameslice", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, baseSlice), true)
})

t.Run("samesize_differentorder", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeDiffOrder), true)
})

t.Run("samesize_sameorder", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeSameOrder), true)
})

t.Run("negative_samesize_differentcontent", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeDiffContent), false)
})

t.Run("negative_differentsizesmaller", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, diffSmaller), false)
})

t.Run("negative_differentsizelarger", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, diffLarger), false)
})

t.Run("empty_both", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual([]string{}, []string{}), true)
})

t.Run("negative_empty_a", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual([]string{}, baseSlice), false)
})

t.Run("negative_empty_b", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, []string{}), false)
})

t.Run("negative_samesize_emptystring", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeEmptyString), false)
})

t.Run("samesize_oneemptystring_difforder", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeEmptyString, sameSizeEmptyStringDiffOrder), true)
})

t.Run("negative_samesize_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(baseSlice, sameSizeAllEmptyStrings), false)
})

t.Run("negative_differentsizesmaller_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeAllEmptyStrings, diffSizeSmallerAllEmptyStrings), false)
})

t.Run("negative_differentsizelarger_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeAllEmptyStrings, diffSizeLargerAllEmptyStrings), false)
})

t.Run("sameslice_allemptystrings", func(t *testing.T) {
assert.Equal(t, contentGuardHrefsAreEqual(sameSizeAllEmptyStrings, sameSizeAllEmptyStrings), true)
})
}
Loading

0 comments on commit 97ee9c9

Please sign in to comment.