Skip to content

Commit

Permalink
feat: implement owner reference for secrets (#43)
Browse files Browse the repository at this point in the history
Signed-off-by: Ariel Septon <arielsepton@Ariels-MBP.lan>
Co-authored-by: Ariel Septon <arielsepton@Ariels-MBP.lan>
  • Loading branch information
arielsepton and Ariel Septon authored Aug 5, 2024
1 parent 0cc8127 commit 0aa30d5
Show file tree
Hide file tree
Showing 24 changed files with 365 additions and 124 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/promote.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g. v1.0.3)"
description: "Release version (e.g. v1.0.4)"
required: true
channel:
description: "Release channel"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g. v1.0.3)"
description: "Release version (e.g. v1.0.4)"
required: true
message:
description: "Tag message"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ To install `provider-http`, you have two options:
1. Using the Crossplane CLI in a Kubernetes cluster where Crossplane is installed:

```console
crossplane xpkg install provider xpkg.upbound.io/crossplane-contrib/provider-http:v1.0.3
crossplane xpkg install provider xpkg.upbound.io/crossplane-contrib/provider-http:v1.0.4
```

2. Manually creating a Provider by applying the following YAML:
Expand All @@ -20,7 +20,7 @@ To install `provider-http`, you have two options:
metadata:
name: provider-http
spec:
package: "xpkg.upbound.io/crossplane-contrib/provider-http:v1.0.3"
package: "xpkg.upbound.io/crossplane-contrib/provider-http:v1.0.4"
```
## Supported Resources
Expand Down
3 changes: 3 additions & 0 deletions apis/disposablerequest/v1alpha2/disposablerequest_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ type SecretInjectionConfig struct {

// ResponsePath is is a jq filter expression represents the path in the response where the secret value will be extracted from.
ResponsePath string `json:"responsePath"`

// SetOwnerReference determines whether to set the owner reference on the Kubernetes secret.
SetOwnerReference bool `json:"setOwnerReference,omitempty"`
}

// SecretRef contains the name and namespace of a Kubernetes secret.
Expand Down
3 changes: 3 additions & 0 deletions apis/request/v1alpha2/request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ type SecretInjectionConfig struct {

// ResponsePath is is a jq filter expression represents the path in the response where the secret value will be extracted from.
ResponsePath string `json:"responsePath"`

// SetOwnerReference determines whether to set the owner reference on the Kubernetes secret.
SetOwnerReference bool `json:"setOwnerReference,omitempty"`
}

// SecretRef contains the name and namespace of a Kubernetes secret.
Expand Down
47 changes: 29 additions & 18 deletions cluster/test/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,39 @@ cat <<EOF | ${KUBECTL} apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo
name: flask-api
namespace: default
labels:
app: todo
app: flask-api
spec:
replicas: 1
replicas: 3
selector:
matchLabels:
app: todo
app: flask-api
template:
metadata:
labels:
app: todo
app: flask-api
spec:
containers:
- name: todo
image: danielsinai/todo:v1.0.0
env:
- name: PORT
value: "80"
- name: flask-api
image: arielsepton/flask-api:latest
ports:
- containerPort: 80
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: todo
name: flask-api
namespace: default
spec:
type: ClusterIP
selector:
app: flask-api
ports:
- name: "todo"
- protocol: TCP
port: 80
selector:
app: todo
targetPort: 5000
type: ClusterIP
EOF

cat <<EOF | kubectl apply -f -
Expand All @@ -70,9 +70,20 @@ cat <<EOF | kubectl apply -f -
kind: Secret
apiVersion: v1
metadata:
name: password
name: user-password
namespace: crossplane-system
type: Opaque
data:
password: bXktc2VjcmV0LXZhbHVl
EOF

cat <<EOF | kubectl apply -f -
kind: Secret
apiVersion: v1
metadata:
name: basic-auth
namespace: crossplane-system
type: Opaque
data:
secretKey: bXktc2VjcmV0LXZhbHVl
token: bXktc2VjcmV0LXZhbHVl
EOF
2 changes: 1 addition & 1 deletion deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ kind: Provider
metadata:
name: http-provider
spec:
package: xpkg.upbound.io/crossplane-contrib/provider-http:v1.0.3
package: xpkg.upbound.io/crossplane-contrib/provider-http:v1.0.4
controllerConfigRef:
name: debug-config

Expand Down
32 changes: 18 additions & 14 deletions examples/sample/disposablerequest.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
apiVersion: http.crossplane.io/v1alpha2
kind: DisposableRequest
metadata:
name: health-check
name: send-notification
spec:
deletionPolicy: Orphan
forProvider:
# Injecting data from secrets is possible, simply use the following syntax: {{ name:namespace:key }} (supported for body and headers only)
url: http://todo.default.svc.cluster.local/health-check
url: http://flask-api.default.svc.cluster.local/v1/notify
method: POST
body: |
{
"check_type": "simple",
"additional_info": "optional",
"password": "secretdata {{ password:crossplane-system:secretKey }}"
"recipient": "user@example.com",
"subject": "Alert",
"message": "Your action is required immediately."
}
headers:
User-Agent:
- "Crossplane Health Checker"
Content-Type:
- application/json
Authorization:
- "Bearer {{ auth:default:token }}"
insecureSkipTLSVerify: true

# The 'expectedResponse' field is optional. If used, also set 'rollbackRetriesLimit', which determines the number of HTTP requests to be sent until the jq query returns true.
# expectedResponse: '.body.job_status == "success"'
expectedResponse: '.body.status == "sent"'
rollbackRetriesLimit: 5
waitTimeout: 5m

Expand All @@ -35,15 +35,19 @@ spec:
# Secrets receiving patches from response data
secretInjectionConfigs:
- secretRef:
name: response-secret
name: notification-response
namespace: default
secretKey: extracted-data
responsePath: .body.reminder
secretKey: notification-status
responsePath: .body.status
# setOwnerReference determines if the secret should be deleted when the associated resource is deleted.
# When injecting multiple keys into the same secret, ensure this field is set consistently for all keys.
setOwnerReference: true
- secretRef:
name: response-secret
name: notification-response
namespace: default
secretKey: extracted-data-headers
responsePath: .headers.Try[0]
secretKey: notification-id
responsePath: .body.id
setOwnerReference: true
providerConfigRef:
name: http-conf
# TODO: check if it's possible to modify the deletionPolicy to be orphan by default.
40 changes: 40 additions & 0 deletions examples/sample/disposablerequest_jwt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: http.crossplane.io/v1alpha2
kind: DisposableRequest
metadata:
name: obtain-jwt-token
spec:
deletionPolicy: Orphan
forProvider:
insecureSkipTLSVerify: true

# Injecting data from secrets is possible, simply use the following syntax: {{ name:namespace:key }} (supported for body and headers only)
headers:
Authorization:
- "Basic {{ basic-auth:crossplane-system:token }}"
url: http://flask-api.default.svc.cluster.local/v1/login
method: POST

shouldLoopInfinitely: true
nextReconcile: 72h # 3 days

# waitTimeout: 5m

# Indicates whether the reconciliation should loop indefinitely. If `rollbackRetriesLimit` is set and the request returns an error, it will stop reconciliation once the limit is reached.
# shouldLoopInfinitely: true

# Specifies the duration after which the next reconcile should occur.
# nextReconcile: 3m

# Secrets receiving patches from response data
secretInjectionConfigs:
- secretRef:
name: obtained-token
namespace: crossplane-system
secretKey: token
responsePath: .body.token
# setOwnerReference determines if the secret should be deleted when the associated resource is deleted.
# When injecting multiple keys into the same secret, ensure this field is set consistently for all keys.
setOwnerReference: true
providerConfigRef:
name: http-conf
# TODO: check if it's possible to modify the deletionPolicy to be orphan by default.
41 changes: 25 additions & 16 deletions examples/sample/request.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: http.crossplane.io/v1alpha2
kind: Request
metadata:
name: laundry
name: manage-user
spec:
forProvider:
# Injecting data from secrets is possible, simply use the following syntax: {{ name:namespace:key }} (supported for body and headers only)
Expand All @@ -13,21 +13,22 @@ spec:
Authorization:
- ("Bearer {{ auth:default:token }}")
payload:
baseUrl: http://todo.default.svc.cluster.local/todos
baseUrl: http://flask-api.default.svc.cluster.local/v1/users
body: |
{
"name": "Do Laundry",
"reminder": "Every 1 hour",
"responsible": "Dan",
"password": "secretdata {{ password:crossplane-system:secretKey }}"
"username": "mock_user",
"password": "secretdata {{ user-password:crossplane-system:password }}",
"email": "mock_user@example.com",
"age": 30
}
mappings:
- method: "POST"
body: |
{
todo_name: .payload.body.name,
reminder: .payload.body.reminder,
responsible: .payload.body.responsible,
username: .payload.body.username,
email: .payload.body.email,
age: .payload.body.age,
password: .payload.body.password
}
url: .payload.baseUrl
headers:
Expand All @@ -42,9 +43,8 @@ spec:
- method: "PUT"
body: |
{
todo_name: .payload.body.name,
reminder: .payload.body.reminder,
responsible: .payload.body.responsible
email: .payload.body.email,
age: .payload.body.age
}
url: (.payload.baseUrl + "/" + (.response.body.id|tostring))
- method: "DELETE"
Expand All @@ -55,12 +55,21 @@ spec:
- secretRef:
name: response-secret
namespace: default
secretKey: extracted-data
responsePath: .body.reminder
secretKey: extracted-user-email
responsePath: .body.email
# setOwnerReference determines if the secret should be deleted when the associated resource is deleted.
# When injecting multiple keys into the same secret, ensure this field is set consistently for all keys.
setOwnerReference: true
- secretRef:
name: response-secret
namespace: default
secretKey: extracted-data-headers
responsePath: .headers.Try[0]
secretKey: extracted-header-data
responsePath: .headers."X-Secret-Header"[0]
setOwnerReference: true
- secretRef:
name: response-user-password
namespace: default
secretKey: extracted-user-password
responsePath: .body.password
providerConfigRef:
name: http-conf
13 changes: 10 additions & 3 deletions internal/controller/disposablerequest/disposablerequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
apisv1alpha1 "github.com/crossplane-contrib/provider-http/apis/v1alpha1"
httpClient "github.com/crossplane-contrib/provider-http/internal/clients/http"
"github.com/crossplane-contrib/provider-http/internal/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
Expand Down Expand Up @@ -171,12 +172,12 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
}

func (c *external) deployAction(ctx context.Context, cr *v1alpha2.DisposableRequest) error {
sensitiveBody, err := datapatcher.PatchSecretsIntoBody(ctx, c.localKube, cr.Spec.ForProvider.Body)
sensitiveBody, err := datapatcher.PatchSecretsIntoBody(ctx, c.localKube, cr.Spec.ForProvider.Body, c.logger)
if err != nil {
return err
}

sensitiveHeaders, err := datapatcher.PatchSecretsIntoHeaders(ctx, c.localKube, cr.Spec.ForProvider.Headers)
sensitiveHeaders, err := datapatcher.PatchSecretsIntoHeaders(ctx, c.localKube, cr.Spec.ForProvider.Headers, c.logger)
if err != nil {
return err
}
Expand Down Expand Up @@ -288,7 +289,13 @@ func (c *external) Delete(_ context.Context, _ resource.Managed) error {

func (c *external) patchResponseToSecret(ctx context.Context, cr *v1alpha2.DisposableRequest, response *httpClient.HttpResponse) {
for _, ref := range cr.Spec.ForProvider.SecretInjectionConfigs {
err := datapatcher.PatchResponseToSecret(ctx, c.localKube, c.logger, response, ref.ResponsePath, ref.SecretKey, ref.SecretRef.Name, ref.SecretRef.Namespace)
var owner metav1.Object = nil

if ref.SetOwnerReference {
owner = cr
}

err := datapatcher.PatchResponseToSecret(ctx, c.localKube, c.logger, response, ref.ResponsePath, ref.SecretKey, ref.SecretRef.Name, ref.SecretRef.Namespace, owner)
if err != nil {
c.logger.Info(fmt.Sprintf(errPatchDataToSecret, ref.SecretRef.Name, ref.SecretRef.Namespace, ref.SecretKey, err.Error()))
}
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/request/observe.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (c *external) requestDetails(ctx context.Context, cr *v1alpha2.Request, met
return requestgen.RequestDetails{}, errors.Errorf(errMappingNotFound, method)
}

return generateValidRequestDetails(ctx, c.localKube, cr, mapping)
return c.generateValidRequestDetails(ctx, cr, mapping)
}

// isErrorMappingNotFound checks if the provided error indicates that the
Expand Down
Loading

0 comments on commit 0aa30d5

Please sign in to comment.