Skip to content

Commit d92660a

Browse files
authored
feat(policies): validate policy attachments against remote provider (#1668)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 1a222a5 commit d92660a

File tree

2 files changed

+96
-0
lines changed

2 files changed

+96
-0
lines changed

app/controlplane/pkg/biz/workflowcontract.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,19 @@ func (uc *WorkflowContractUseCase) ValidateContractPolicies(rawSchema []byte, to
359359
return nil
360360
}
361361

362+
func (uc *WorkflowContractUseCase) ValidatePolicyAttachment(providerName string, att *schemav1.PolicyAttachment, token string) error {
363+
provider, err := uc.findProvider(providerName)
364+
if err != nil {
365+
return err
366+
}
367+
368+
if err = provider.ValidateAttachment(att, token); err != nil {
369+
return fmt.Errorf("invalid attachment: %w", err)
370+
}
371+
372+
return nil
373+
}
374+
362375
func (uc *WorkflowContractUseCase) findAndValidatePolicy(att *schemav1.PolicyAttachment, token string) (*schemav1.Policy, error) {
363376
var policy *schemav1.Policy
364377

@@ -370,6 +383,11 @@ func (uc *WorkflowContractUseCase) findAndValidatePolicy(att *schemav1.PolicyAtt
370383
// [chainloop://][provider:][org_name/]name
371384
if loader.IsProviderScheme(att.GetRef()) {
372385
pr := loader.ProviderParts(att.GetRef())
386+
// Validate attachment
387+
if err := uc.ValidatePolicyAttachment(pr.Provider, att, token); err != nil {
388+
return nil, err
389+
}
390+
373391
remotePolicy, err := uc.GetPolicy(pr.Provider, pr.Name, pr.OrgName, token)
374392
if err != nil {
375393
return nil, err

app/controlplane/pkg/policies/policyprovider.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package policies
1717

1818
import (
19+
"bytes"
1920
"encoding/json"
2021
"fmt"
2122
"io"
@@ -31,6 +32,7 @@ import (
3132

3233
const (
3334
policiesEndpoint = "policies"
35+
validateAction = "validate"
3436
groupsEndpoint = "groups"
3537

3638
digestParam = "digest"
@@ -50,6 +52,15 @@ type ProviderResponse struct {
5052
Raw *RawMessage `json:"raw"`
5153
}
5254

55+
type ValidateRequest struct {
56+
PolicyAttachment string `json:"policy_attachment"`
57+
}
58+
59+
type ValidateResponse struct {
60+
Valid bool `json:"valid"`
61+
ValidationErrors []string `json:"validationErrors"`
62+
}
63+
5364
type RawMessage struct {
5465
Body []byte `json:"body"`
5566
Format string `json:"format"`
@@ -88,6 +99,73 @@ func (p *PolicyProvider) Resolve(policyName, orgName, token string) (*schemaapi.
8899
return &policy, createRef(url, policyName, providerDigest, orgName), nil
89100
}
90101

102+
func (p *PolicyProvider) ValidateAttachment(att *schemaapi.PolicyAttachment, token string) error {
103+
endpoint, err := url.JoinPath(p.url, policiesEndpoint, validateAction)
104+
if err != nil {
105+
return fmt.Errorf("invalid url: %w", err)
106+
}
107+
url, err := url.Parse(endpoint)
108+
if err != nil {
109+
return fmt.Errorf("error parsing policy provider URL: %w", err)
110+
}
111+
112+
attBody, err := protojson.Marshal(att)
113+
if err != nil {
114+
return fmt.Errorf("error serializing policy attachment: %w", err)
115+
}
116+
117+
validateReq := &ValidateRequest{
118+
PolicyAttachment: string(attBody),
119+
}
120+
121+
reqBody, err := json.Marshal(validateReq)
122+
if err != nil {
123+
return fmt.Errorf("error serializing policy validation request: %w", err)
124+
}
125+
126+
req, err := http.NewRequest("POST", url.String(), bytes.NewReader(reqBody))
127+
if err != nil {
128+
return fmt.Errorf("error creating policy request: %w", err)
129+
}
130+
131+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
132+
req.Header.Set("Content-Type", "application/json")
133+
134+
// make the request
135+
resp, err := http.DefaultClient.Do(req)
136+
if err != nil {
137+
return fmt.Errorf("error executing policy request: %w", err)
138+
}
139+
140+
if resp.StatusCode != http.StatusOK {
141+
if resp.StatusCode == http.StatusNotFound {
142+
// Ignore endpoint not found as it might not be implemented by the provider
143+
return nil
144+
}
145+
146+
return fmt.Errorf("expected status code 200 but got %d", resp.StatusCode)
147+
}
148+
149+
resBytes, err := io.ReadAll(resp.Body)
150+
if err != nil {
151+
return fmt.Errorf("error reading validation response: %w", err)
152+
}
153+
154+
defer resp.Body.Close()
155+
156+
// unmarshall response
157+
var response ValidateResponse
158+
if err := json.Unmarshal(resBytes, &response); err != nil {
159+
return fmt.Errorf("error unmarshalling validation response: %w", err)
160+
}
161+
162+
if !response.Valid {
163+
return fmt.Errorf("validation failures: %v", response.ValidationErrors)
164+
}
165+
166+
return nil
167+
}
168+
91169
// ResolveGroup calls remote provider for retrieving a policy group definition
92170
func (p *PolicyProvider) ResolveGroup(groupName, orgName, token string) (*schemaapi.PolicyGroup, *PolicyReference, error) {
93171
if groupName == "" || token == "" {

0 commit comments

Comments
 (0)