diff --git a/pkg/clients/pulp/guards_composite.go b/pkg/clients/pulp/guards_composite.go index 665c234b4..64f9080b0 100644 --- a/pkg/clients/pulp/guards_composite.go +++ b/pkg/clients/pulp/guards_composite.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/redhatinsights/edge-api/pkg/ptr" + feature "github.com/redhatinsights/edge-api/unleash/features" "github.com/sirupsen/logrus" ) @@ -107,12 +108,27 @@ func (ps *PulpService) ContentGuardEnsure(ctx context.Context, orgID string) (*C return nil, err } - rcg, err := ps.RbacGuardEnsure(ctx) - if err != nil { - return nil, err + var contentGuardHref *string + + if feature.PulpIntegrationTurnpikeGuard.IsEnabled() { + logrus.WithContext(ctx).Debug("Creating turnpike content guard") + tpcg, err := ps.TurnpikeGuardEnsure(ctx) + if err != nil { + return nil, err + } + + contentGuardHref = tpcg.PulpHref + } else { + logrus.WithContext(ctx).Debug("Creating RBAC content guard") + rcg, err := ps.RbacGuardEnsure(ctx) + if err != nil { + return nil, err + } + + contentGuardHref = rcg.PulpHref } - ccg, err := ps.CompositeGuardEnsure(ctx, orgID, *hcg.PulpHref, *rcg.PulpHref) + ccg, err := ps.CompositeGuardEnsure(ctx, orgID, *hcg.PulpHref, *contentGuardHref) if err != nil { return nil, err } diff --git a/pkg/clients/pulp/guards_turnpike.go b/pkg/clients/pulp/guards_turnpike.go new file mode 100644 index 000000000..b3498e98f --- /dev/null +++ b/pkg/clients/pulp/guards_turnpike.go @@ -0,0 +1,171 @@ +package pulp + +import ( + "context" + "errors" + "fmt" + + "github.com/google/uuid" + "github.com/redhatinsights/edge-api/config" + "github.com/redhatinsights/edge-api/pkg/ptr" + "github.com/sirupsen/logrus" +) + +const TurnpikeGuardName = "ostree_turnpike_guard" +const TurnpikeJQFilter = ".identity.x509.subject_dn" + +// TurnpikeGuardList returns a list of header guards. The nameFilter can be used to filter the results. +func (ps *PulpService) TurnpikeGuardList(ctx context.Context, nameFilter string) ([]HeaderContentGuardResponse, error) { + req := ContentguardsCoreHeaderListParams{ + Limit: &DefaultPageSize, + } + if nameFilter != "" { + req.Name = &nameFilter + } + + resp, err := ps.cwr.ContentguardsCoreHeaderListWithResponse(ctx, ps.dom, &req, addAuthenticationHeader) + if err != nil { + return nil, err + } + + if resp.JSON200 == nil { + return nil, fmt.Errorf("unexpected response: %d, body: %s", resp.StatusCode(), string(resp.Body)) + } + + if resp.JSON200.Count > DefaultPageSize { + return nil, fmt.Errorf("default page size too small: %d", resp.JSON200.Count) + } + + if resp.JSON200.Count == 0 || resp.JSON200.Results[0].PulpHref == nil { + return nil, ErrRecordNotFound + } + + return resp.JSON200.Results, nil +} + +// TurnpikeGuardRead returns the Turnpike guard with the given ID. +func (ps *PulpService) TurnpikeGuardRead(ctx context.Context, id uuid.UUID) (*HeaderContentGuardResponse, error) { + req := ContentguardsCoreHeaderReadParams{} + resp, err := ps.cwr.ContentguardsCoreHeaderReadWithResponse(ctx, ps.dom, id, &req, addAuthenticationHeader) + + if err != nil { + return nil, err + } + + if resp.JSON200 == nil { + return nil, fmt.Errorf("unexpected response: %d, body: %s", resp.StatusCode(), string(resp.Body)) + } + + return resp.JSON200, nil +} + +// TurnpikeGuardFind returns the Turnpike guard. +func (ps *PulpService) TurnpikeGuardFind(ctx context.Context) (*HeaderContentGuardResponse, error) { + hgl, err := ps.TurnpikeGuardList(ctx, TurnpikeGuardName) + if err != nil { + logrus.WithFields(logrus.Fields{ + "name": TurnpikeGuardName, + "error": err.Error(), + }).Error("Turnpike content guard not found") + + if errors.Is(err, ErrRecordNotFound) { + return nil, ErrRecordNotFound + } + + return nil, err + } + + id := ScanUUID(hgl[0].PulpHref) + return ps.TurnpikeGuardRead(ctx, id) +} + +// TurnpikeGuardCreate creates a new Turnpike guard and returns it. +func (ps *PulpService) TurnpikeGuardCreate(ctx context.Context) (*HeaderContentGuardResponse, error) { + cfg := config.Get() + + req := HeaderContentGuard{ + Name: TurnpikeGuardName, + Description: ptr.To("EDGE"), + HeaderName: "x-rh-identity", + HeaderValue: cfg.Pulp.GuardSubjectDN, + JqFilter: ptr.To(TurnpikeJQFilter), + } + + resp, err := ps.cwr.ContentguardsCoreHeaderCreateWithResponse(ctx, ps.dom, req, addAuthenticationHeader) + + if err != nil { + return nil, err + } + + if resp.JSON201 == nil { + return nil, fmt.Errorf("unexpected response: %d, body: %s", resp.StatusCode(), string(resp.Body)) + } + + return resp.JSON201, nil +} + +// TurnpikeGuardEnsure ensures that the Turnpike guard is created and returns it. The method is idempotent. +func (ps *PulpService) TurnpikeGuardEnsure(ctx context.Context) (*HeaderContentGuardResponse, error) { + cg, err := ps.TurnpikeGuardFind(ctx) + if errors.Is(err, ErrRecordNotFound) { + // turnpike guard is not found, so create one + + cg, err = ps.TurnpikeGuardCreate(ctx) + if err != nil { + return nil, err + } + + return cg, nil + } else if err != nil { + return nil, err + } else if cg == nil { + return nil, fmt.Errorf("unexpected nil guard") + } + + return cg, nil +} + +// TurnpikeGuardDelete deletes the Turnpike guard with the given ID. +func (ps *PulpService) TurnpikeGuardDelete(ctx context.Context, id uuid.UUID) error { + listParams := ContentguardsCoreHeaderListRolesParams{} + roles, err := ps.cwr.ContentguardsCoreHeaderListRolesWithResponse(ctx, ps.dom, id, &listParams, addAuthenticationHeader) + + if err != nil { + return err + } + + if roles.JSON200 == nil { + return fmt.Errorf("unexpected response: %d, body: %s", roles.StatusCode(), string(roles.Body)) + } + + for _, role := range roles.JSON200.Roles { + nr := NestedRole{ + Role: role.Role, + Users: &[]string{}, + } + + logrus.WithContext(ctx).Warnf("removing Turnpike guardrole: %s", role.Role) + removed, err := ps.cwr.ContentguardsCoreHeaderRemoveRoleWithResponse(ctx, ps.dom, id, nr, addAuthenticationHeader) + + if err != nil { + return err + } + + if removed.JSON201 == nil { + return fmt.Errorf("unexpected response: %d, body: %s", removed.StatusCode(), string(removed.Body)) + } + } + + resp, err := ps.cwr.ContentguardsCoreHeaderDelete(ctx, ps.dom, id, addAuthenticationHeader) + + if err != nil { + return err + } + resp.Body.Close() + + if resp.StatusCode != 204 { + return fmt.Errorf("unexpected response: %d", resp.StatusCode) + } + + return nil +} diff --git a/pkg/services/repostore/pulpstore.go b/pkg/services/repostore/pulpstore.go index 9cbcbdc14..1af7c1cb7 100644 --- a/pkg/services/repostore/pulpstore.go +++ b/pkg/services/repostore/pulpstore.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/redhatinsights/edge-api/config" "github.com/redhatinsights/edge-api/pkg/clients/pulp" + feature "github.com/redhatinsights/edge-api/unleash/features" log "github.com/sirupsen/logrus" ) @@ -172,7 +173,7 @@ func distributionURL(ctx context.Context, distBaseURL string, domain string, rep distURL := prodDistURL.String() // temporarily handle stage URLs so Image Builder worker can get to stage Pulp - if strings.Contains(distBaseURL, "stage") { + if feature.PulpIntegrationSwapStageURL.IsEnabled() && strings.Contains(distBaseURL, "stage") { stagePulpURL := fmt.Sprintf("%s/api/pulp-content/%s/%s", cfg.PulpURL, domain, repoName) stageDistURL, err := url.Parse(stagePulpURL) if err != nil { diff --git a/unleash/features/feature.go b/unleash/features/feature.go index 081da2bc5..1288d864f 100644 --- a/unleash/features/feature.go +++ b/unleash/features/feature.go @@ -119,6 +119,12 @@ var PulpIntegrationDisableAWSRepoStore = &Flag{Name: "edge-management.pulp_integ // PulpIntegrationUpdateViaPulp uses the Pulp Distribution URL for image and system updates var PulpIntegrationUpdateViaPulp = &Flag{Name: "edge-management.pulp_integration_updateviapulp", EnvVar: "FEATURE_PULP_INTEGRATION_UPDATEVIAPULP"} +// PulpIntegrationSwapStageURL temporarily uses a specific URL for Image Builder to access Pulp in stage +var PulpIntegrationSwapStageURL = &Flag{Name: "edge-management.pulp_integration_swapstageurl", EnvVar: "FEATURE_PULP_INTEGRATION_SWAPSTAGEURL"} + +// PulpIntegrationTurnpikeContentGuard enables and turnpike content guard and disables the RBAC content guard +var PulpIntegrationTurnpikeGuard = &Flag{Name: "edge-management.pulp_integration_turnpikeguard", EnvVar: "FEATURE_PULP_INTEGRATION_TURNPIKEGUARD"} + // (ADD FEATURE FLAGS ABOVE) // FEATURE FLAG CHECK CODE