Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] - Exposing Integrations #180

Merged
merged 2 commits into from Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,6 +2,7 @@
*.orig
.idea
.vscode
.vim
gcp-auth.json
/*.env
#local dev artifacts
Expand Down
101 changes: 77 additions & 24 deletions pkg/controller/configuration/ensure.go
Expand Up @@ -570,37 +570,65 @@ func (c *Controller) ensureCostStatus(configuration *terraformv1alphav1.Configur
return reconcile.Result{}, nil
}

input := secret.Data["costs.json"]
if len(input) == 0 {
return reconcile.Result{}, nil
}

// @step: parse the cost report json
if secret.Data["costs.json"] != nil {
report := make(map[string]interface{})
if err := json.NewDecoder(bytes.NewReader(secret.Data["costs.json"])).Decode(&report); err != nil {
cond.Failed(err, "Failed to decode the terraform costs report")
values := map[string]float64{
"totalMonthlyCost": 0,
"totalHourlyCost": 0,
}
for key := range values {
value := gjson.GetBytes(input, key)
if !value.Exists() {
cond.ActionRequired("Cost report does not include the %s value", key)

return reconcile.Result{}, err
return reconcile.Result{}, controller.ErrIgnore
}

var monthly, hourly float64
cost, err := strconv.ParseFloat(value.String(), 64)
if err != nil {
cond.ActionRequired("Cost report contains an include value: %q for item: %s", value.String(), key)

if v, ok := report["totalMonthlyCost"].(string); ok {
if x, err := strconv.ParseFloat(v, 64); err == nil {
monthly = x
}
}
if v, ok := report["totalHourlyCost"].(string); ok {
if x, err := strconv.ParseFloat(v, 64); err == nil {
hourly = x
}
return reconcile.Result{}, controller.ErrIgnore
}
values[key] = cost
}

configuration.Status.Costs = &terraformv1alphav1.CostStatus{
Enabled: true,
Hourly: fmt.Sprintf("$%v", hourly),
Monthly: fmt.Sprintf("$%v", monthly),
}
configuration.Status.Costs = &terraformv1alphav1.CostStatus{
Enabled: true,
Hourly: fmt.Sprintf("$%v", values["totalHourlyCost"]),
Monthly: fmt.Sprintf("$%v", values["totalMonthlyCost"]),
}

// @step: update the prometheus metrics
monthlyCostMetric.WithLabelValues(labels...).Set(monthly)
hourlyCostMetric.WithLabelValues(labels...).Set(hourly)
// @step: update the prometheus metrics
monthlyCostMetric.WithLabelValues(labels...).Set(values["totalMonthlyCost"])
hourlyCostMetric.WithLabelValues(labels...).Set(values["totalHourlyCost"])

// @step: copy the infracost report into the configuration namespace
copied := &v1.Secret{}
copied.Namespace = configuration.GetNamespace()
copied.Name = configuration.GetTerraformCostSecretName()
copied.Labels = map[string]string{
terraformv1alphav1.ConfigurationNameLabel: configuration.GetName(),
terraformv1alphav1.ConfigurationUIDLabel: string(configuration.GetUID()),
}
copied.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: terraformv1alphav1.SchemeGroupVersion.String(),
Kind: terraformv1alphav1.ConfigurationKind,
Name: configuration.GetName(),
UID: configuration.GetUID(),
},
}
copied.Data = secret.Data

if err := kubernetes.CreateOrForceUpdate(ctx, c.cc, copied); err != nil {
cond.Failed(err, "Failed to create or update the terraform costs secret")

return reconcile.Result{}, err
}

return reconcile.Result{}, nil
Expand All @@ -610,6 +638,7 @@ func (c *Controller) ensureCostStatus(configuration *terraformv1alphav1.Configur
// ensurePolicyStatus is responsible for checking the checkov results and refusing to continue if failed
func (c *Controller) ensurePolicyStatus(configuration *terraformv1alphav1.Configuration, state *state) controller.EnsureFunc {
cond := controller.ConditionMgr(configuration, terraformv1alphav1.ConditionTerraformPolicy, c.recorder)
key := "results_json.json"

return func(ctx context.Context) (reconcile.Result, error) {
switch {
Expand Down Expand Up @@ -637,7 +666,7 @@ func (c *Controller) ensurePolicyStatus(configuration *terraformv1alphav1.Config
}

// @step: retrieve summary from the report
checksFailed := gjson.GetBytes(secret.Data["results_json.json"], "summary.failed")
checksFailed := gjson.GetBytes(secret.Data[key], "summary.failed")
if !checksFailed.Exists() {
cond.Failed(errors.New("missing report"), "Security report does not contain a summary of finding, please contact platform administrator")

Expand All @@ -650,6 +679,30 @@ func (c *Controller) ensurePolicyStatus(configuration *terraformv1alphav1.Config
return reconcile.Result{}, controller.ErrIgnore
}

// @step: copy the report into the configuration namespace
copied := &v1.Secret{}
copied.Namespace = configuration.GetNamespace()
copied.Name = configuration.GetTerraformPolicySecretName()
copied.Labels = map[string]string{
terraformv1alphav1.ConfigurationNameLabel: configuration.GetName(),
terraformv1alphav1.ConfigurationUIDLabel: string(configuration.GetUID()),
}
copied.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: terraformv1alphav1.SchemeGroupVersion.String(),
Kind: terraformv1alphav1.ConfigurationKind,
Name: configuration.GetName(),
UID: configuration.GetUID(),
},
}
copied.Data = secret.Data

if err := kubernetes.CreateOrForceUpdate(ctx, c.cc, copied); err != nil {
cond.Failed(err, "Failed to create or update the terraform policy secret")

return reconcile.Result{}, err
}

if checksFailed.Int() > 0 {
cond.ActionRequired("Configuration has failed security policy, refusing to continue")

Expand Down
35 changes: 35 additions & 0 deletions pkg/controller/configuration/reconcile_test.go
Expand Up @@ -680,6 +680,19 @@ var _ = Describe("Configuration Controller", func() {
Expect(configuration.Status.Costs.Monthly).To(Equal("$100"))
Expect(configuration.Status.Costs.Hourly).To(Equal("$0.01"))
})

It("should have copied the secret into the configuration namespace", func() {
expected := "\n{\n\t\"totalHourlyCost\": \"0.01\",\n \"totalMonthlyCost\": \"100.00\"\n}\n"
secret := &v1.Secret{}
secret.Namespace = configuration.Namespace
secret.Name = configuration.GetTerraformCostSecretName()
found, err := kubernetes.GetIfExists(context.TODO(), cc, secret)

Expect(err).ToNot(HaveOccurred())
Expect(found).To(BeTrue())
Expect(secret.Data).To(HaveKey("costs.json"))
Expect(string(secret.Data["costs.json"])).To(Equal(expected))
})
})
})

Expand Down Expand Up @@ -1447,6 +1460,17 @@ terraform {
Expect(cc.List(context.TODO(), list, client.InNamespace(ctrl.ControllerNamespace))).ToNot(HaveOccurred())
Expect(len(list.Items)).To(Equal(1))
})

It("should copied the report into the configuration namespace", func() {
secret := &v1.Secret{}
secret.Namespace = configuration.Namespace
secret.Name = configuration.GetTerraformPolicySecretName()
found, err := kubernetes.GetIfExists(context.TODO(), ctrl.cc, secret)
Expect(err).ToNot(HaveOccurred())
Expect(found).To(BeTrue())
Expect(secret.Data).To(HaveKey("results_json.json"))
Expect(string(secret.Data["results_json.json"])).To(ContainSubstring("summary"))
})
})

When("policy report contains no failed checks", func() {
Expand Down Expand Up @@ -1483,6 +1507,17 @@ terraform {
Expect(cc.List(context.TODO(), list, client.InNamespace(ctrl.ControllerNamespace))).ToNot(HaveOccurred())
Expect(len(list.Items)).To(Equal(2))
})

It("should copied the report into the configuration namespace", func() {
secret := &v1.Secret{}
secret.Namespace = configuration.Namespace
secret.Name = configuration.GetTerraformPolicySecretName()
found, err := kubernetes.GetIfExists(context.TODO(), ctrl.cc, secret)
Expect(err).ToNot(HaveOccurred())
Expect(found).To(BeTrue())
Expect(secret.Data).To(HaveKey("results_json.json"))
Expect(string(secret.Data["results_json.json"])).To(ContainSubstring("summary"))
})
})
})
})
Expand Down
1 change: 0 additions & 1 deletion test/e2e/check-suite.sh
Expand Up @@ -55,7 +55,6 @@ run_checks() {
"${UNITS}/cloud/${CLOUD}/provider.bats"
"${UNITS}/cloud/${CLOUD}/plan.bats"
"${UNITS}/plan.bats"
"${UNITS}/costs.bats"
"${UNITS}/apply.bats"
"${UNITS}/cloud/${CLOUD}/confirm.bats"
"${UNITS}/drift.bats"
Expand Down
9 changes: 9 additions & 0 deletions test/e2e/integration/checkov.bats
Expand Up @@ -104,6 +104,15 @@ EOF
[[ "$status" -eq 0 ]]
}

@test "We should have a copy the policy report in the configuration namespace" {
UUID=$(kubectl -n ${APP_NAMESPACE} get configuration ${RESOURCE_NAME} -o json | jq -r '.metadata.uid')
[[ "$status" -eq 0 ]]
runit "kubectl -n ${APP_NAMESPACE} get secret policy-${UUID}"
[[ "$status" -eq 0 ]]
runit "kubectl -n ${APP_NAMESPACE} get secret policy-${UUID} -o json" "jq -r '.data.results_json.json'"
[[ "$status" -eq 0 ]]
}

@test "We should see the conditions indicate the configuration failed policy" {
POD=$(kubectl -n ${APP_NAMESPACE} get pod -l terraform.appvia.io/configuration=${RESOURCE_NAME} -l terraform.appvia.io/stage=plan -o json | jq -r '.items[0].metadata.name')
[[ "$status" -eq 0 ]]
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/integration/cloud/aws/infracost.bats
Expand Up @@ -18,15 +18,16 @@
load ../../../lib/helper

setup() {
[[ ! -f ${BATS_PARENT_TMPNAME}.skip ]] || skip "skip remaining tests"
[[ ! -f "${BATS_PARENT_TMPNAME}.skip" ]] || skip "skip remaining tests"
}

teardown() {
[[ -n "$BATS_TEST_COMPLETED" ]] || touch ${BATS_PARENT_TMPNAME}.skip
}

@test "We should have a token for the infracost integration" {
[[ -z ${INFRACOST_API_KEY} ]] && touch ${BATS_PARENT_TMPNAME}.skip
[[ -z "${INFRACOST_API_KEY}" ]] && touch ${BATS_PARENT_TMPNAME}.skip
[[ "$status" -eq 0 ]]
}

@test "We should be able to create a configuration which costs money on aws" {
Expand Down
51 changes: 0 additions & 51 deletions test/e2e/integration/costs.bats

This file was deleted.

32 changes: 32 additions & 0 deletions test/e2e/integration/plan.bats
Expand Up @@ -85,3 +85,35 @@ teardown() {
runit "kubectl -n ${APP_NAMESPACE} logs ${POD} 2>&1" "grep -q '\[build\] completed'"
[[ "$status" -eq 0 ]]
}

@test "We should have a secret in the terraform namespace containing the report" {
UUID=$(kubectl -n ${APP_NAMESPACE} get configuration ${RESOURCE_NAME} -o json | jq -r '.metadata.uid')
[[ "$status" -eq 0 ]]

runit "kubectl -n ${NAMESPACE} get secret costs-${UUID}"
[[ "$status" -eq 0 ]]
runit "kubectl -n ${NAMESPACE} get secret costs-${UUID} -o json" "jq -r '.data[\"costs.json\"]'"
[[ "$status" -eq 0 ]]
}

@test "We should see the cost integration is enabled" {
runit "kubectl -n ${APP_NAMESPACE} get configuration ${RESOURCE_NAME} -o json" "jq -r '.status.costs.enabled' | grep -q true"
[[ "$status" -eq 0 ]]
}

@test "We should see the cost associated to the configuration" {
runit "kubectl -n ${APP_NAMESPACE} get configuration ${RESOURCE_NAME} -o json" "jq -r '.status.costs.monthly' | grep -q '\$0'"
[[ "$status" -eq 0 ]]
runit "kubectl -n ${APP_NAMESPACE} get configuration ${RESOURCE_NAME} -o json" "jq -r '.status.costs.hourly' | grep -q '\$0'"
[[ "$status" -eq 0 ]]
}

@test "We should have a copy of the infracost report in the configuration namespace" {
UUID=$(kubectl -n ${APP_NAMESPACE} get configuration ${RESOURCE_NAME} -o json | jq -r '.metadata.uid')
[[ "$status" -eq 0 ]]

runit "kubectl -n ${APP_NAMESPACE} get secret costs-${UUID}"
[[ "$status" -eq 0 ]]
runit "kubectl -n ${APP_NAMESPACE} get secret costs-${UUID} -o json" "jq -r '.data[\"costs.json\"]'"
[[ "$status" -eq 0 ]]
}