diff --git a/.github/actions/setup-helm/action.yaml b/.github/actions/setup-helm/action.yaml new file mode 100644 index 0000000..45beba5 --- /dev/null +++ b/.github/actions/setup-helm/action.yaml @@ -0,0 +1,13 @@ +name: Setup Helm +description: This action sets up Docker, kubectl, and Helm in the GitHub Actions environment. +runs: + using: "composite" + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set up kubectl + uses: azure/setup-kubectl@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 diff --git a/.github/actions/wait/action.yaml b/.github/actions/wait/action.yaml new file mode 100644 index 0000000..c85b3f2 --- /dev/null +++ b/.github/actions/wait/action.yaml @@ -0,0 +1,34 @@ +name: Setup SOPS +description: This action sets up Docker, kubectl, and Helm in the GitHub Actions environment. + +inputs: + resource: + required: true + description: "Resource to wait for" + max_retries: + required: false + default: "10" + description: "Resource to wait for" + +runs: + using: "composite" + steps: + - name: Wait for ${{ inputs.resource }} to be ready + shell: bash + run: | + retry=1 + while [[ $retry -le ${{ inputs.max_retries }} ]]; do + if kubectl wait ${{ inputs.resource }} --for=condition=Ready --all --timeout 1s; then + echo "Succeeded." + break + else + echo "Attempt $retry/${{ inputs.max_retries }} failed. Retrying in 10 seconds..." + sleep 10 + ((retry++)) + fi + done + + if [[ $retry -gt ${{ inputs.max_retries }} ]]; then + echo "Wait failed after ${{ inputs.max_retries }} attempts." + exit 1 + fi diff --git a/.github/workflows/create-cluster.yaml b/.github/workflows/create-cluster.yaml index 249146a..08c13c2 100644 --- a/.github/workflows/create-cluster.yaml +++ b/.github/workflows/create-cluster.yaml @@ -1,6 +1,12 @@ name: Create Cluster run-name: Creating Cluster on: [push] + +env: + resource_app: app/aws-eu1 + provider_secret: secret/aws-secret + infrastructure_namespace: infrastructure + jobs: SpinUpCluster: runs-on: ubuntu-24.04 @@ -8,41 +14,53 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set up kubectl - uses: azure/setup-kubectl@v4 - - name: Set up Helm - uses: azure/setup-helm@v4 - - - name: Install kind - run: | - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64 - chmod +x ./kind - sudo mv ./kind /usr/local/bin/kind + uses: ./.github/actions/setup-helm - - name: Create kind cluster - run: | - kind create cluster --wait 60s - - - name: Get Cluster status + - name: start minikube + id: minikube + uses: medyagh/setup-minikube@latest + + - name: Spin-up resources + shell: bash run: | - # wait network is ready - kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns - kubectl get nodes -o wide - kubectl get pods -A - - - name: Install Crossplane + cat << EOF > ./foundation/tls.pem + ${{ secrets.SEALED_SECRETS_PKEY }} + EOF + helm dependencies build ./foundation + helm upgrade --install platform-foundation ./foundation --wait + + - name: Wait for Crossplane resources + uses: ./.github/actions/wait + with: + resource: buckets + + - name: Export AWS credentials + id: export_aws_secrets + shell: bash run: | - helm upgrade --install crossplane crossplane-stable/crossplane \ - --namespace crossplane-system \ - --create-namespace \ - --repo https://charts.crossplane.io/stable/ \ - --wait + kubectl wait --timeout 60s --for=create ${provider_secret} -n ${infrastructure_namespace} + kubectl get ${provider_secret} -n ${infrastructure_namespace} --template={{.data.creds}} | base64 -d | tail -n2 | tr -d ' ' >> aws.txt + set -a + . ./aws.txt + set +a + echo "::add-mask::$aws_access_key_id" + echo "::add-mask::$aws_secret_access_key" + cat aws.txt >> "$GITHUB_OUTPUT" + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ steps.export_aws_secrets.outputs.aws_access_key_id }} + aws-secret-access-key: ${{ steps.export_aws_secrets.outputs.aws_secret_access_key }} + aws-region: us-east-2 - - name: Check Crossplane status + - name: List AWS resources + shell: bash + run: aws s3 ls | grep crossplane + + - name: Destroy resources + shell: bash run: | - kubectl get pods -n crossplane-system - kubectl get crds | grep crossplane.io \ No newline at end of file + kubectl delete ${resource_app} + kubectl wait --timeout 60s --for=delete ${resource_app} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..069d5d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.secrets/ +*.tgz +*.lock +*.pem \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d61b9c0 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +## Pre-requisties + +- A `kubectl`, `helm`, and `kubeseal`. + +> Kubeseal is only needed to seal secrets, but not for deployment. + +## Provision resources from local + +Add the private key to `foundation/tls.pem`, then: + +```bash +cp /path/to/my-key/tls.pem ./foundation +helm dependencies build ./foundation +helm upgrade --install platform-foundation ./foundation --namespace platform-foundation --create-namespace --wait +``` + +### Uninstall + +```bash +kubectl delete app/aws-eu1 -n platform-foundation # delete this first to avoid race condition +helm uninstall platform-foundation --namespace platform-foundation --wait +``` + +## View ArgoCD UI + +```bash +kubectl get secret argocd-initial-admin-secret -n platform-foundation --template={{.data.password}} | base64 -d +kubectl port-forward svc/platform-foundation-argocd-server -n platform-foundation 8080:443 +``` + +Use the secret printed and the user `admin` to see the [UI](https://localhost:8080/). + +## Sealing required secrets with Kubeseal + +Create a file `./.secrets/aws-creds.yaml` with: + +``` +[default] +aws_access_key_id = your-key-id-here +aws_secret_access_key = your-secret-here +``` + +Then: + +```bash +kubectl create secret generic aws-secret --from-file=creds=./.secrets/aws-credentials.txt --dry-run=client -o yaml > ./.secrets/aws-creds.yaml +cat ./.secrets/aws-creds.yaml | kubeseal --cert foundation/tls.crt -o yaml -n infrastructure > aws-eu1/infrastructure/templates/aws-creds.yaml +``` diff --git a/aws-eu1/apps/Chart.yaml b/aws-eu1/apps/Chart.yaml new file mode 100644 index 0000000..20c3035 --- /dev/null +++ b/aws-eu1/apps/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: base-cluster-application +description: A Helm chart for Kubernetes + +type: application +version: 0.1.0 +appVersion: "0.1.0" +icon: https://docs.crossplane.io/favicon-32x32.png + +# dependencies: +# - name: crossplane +# version: "1.20.0" +# repository: https://charts.crossplane.io/stable \ No newline at end of file diff --git a/aws-eu1/apps/templates/infrastructure.yaml b/aws-eu1/apps/templates/infrastructure.yaml new file mode 100644 index 0000000..cb1352f --- /dev/null +++ b/aws-eu1/apps/templates/infrastructure.yaml @@ -0,0 +1,26 @@ +{{- if .Values.infrastructureApp.enabled -}} +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: infrastructure + finalizers: + - resources-finalizer.argocd.argoproj.io + annotations: + argocd.argoproj.io/sync-wave: {{ .Values.argoSyncWaves.infrastructureApp | quote }} +spec: + project: default + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + source: + repoURL: https://github.com/diegopso/crossplane-example.git + targetRevision: cluster-creation + path: aws-eu1/infrastructure + destination: + server: https://kubernetes.default.svc + namespace: infrastructure +{{- end -}} \ No newline at end of file diff --git a/aws-eu1/apps/values.yaml b/aws-eu1/apps/values.yaml new file mode 100644 index 0000000..2498c46 --- /dev/null +++ b/aws-eu1/apps/values.yaml @@ -0,0 +1,5 @@ +argoSyncWaves: + infrastructureApp: "-10000" + +infrastructureApp: + enabled: true diff --git a/aws-eu1/infrastructure/Chart.yaml b/aws-eu1/infrastructure/Chart.yaml new file mode 100644 index 0000000..53bb117 --- /dev/null +++ b/aws-eu1/infrastructure/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: crossplane-infrastructure +description: A Helm chart for Kubernetes + +type: application +version: 0.1.0 +appVersion: "1.20.0" +icon: https://docs.crossplane.io/favicon-32x32.png + +# dependencies: +# - name: crossplane +# version: "1.20.0" +# repository: https://charts.crossplane.io/stable \ No newline at end of file diff --git a/aws-eu1/infrastructure/templates/aws-creds.yaml b/aws-eu1/infrastructure/templates/aws-creds.yaml new file mode 100644 index 0000000..06d54d3 --- /dev/null +++ b/aws-eu1/infrastructure/templates/aws-creds.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: bitnami.com/v1alpha1 +kind: SealedSecret +metadata: + creationTimestamp: null + name: aws-secret + namespace: infrastructure +spec: + encryptedData: + creds: AgAqzcrd2DmhzTKYzHG/AG4SrIHwYljieg+Tm9SsB9F4aYo3MZOd1BAcNBIExo1szuFJgTBtPpr8E369LFLLcp12Sc9Q1L2xVLjguS49qGPze69sF4q8SeyT7/fn5RNGbvr4QJ0/RMXh9Lt8+S+0eKWdLTdbE4/ldc0pwEHDSaClAxFgKYbLy+B/h6aVvguAznskHVSE0oCpCM3FFLtLuFkaUXTBwWhdtGRC9auJbisqcS6Vho7qwjdRDx5ZFeRcQ983t4+7KGy51IieDIE9IyW0OQno+H/7V9l5AoZXTt7dJ2FN2skBk9cJMOVPHEgJccosHj8znDPnYG2OROeQJ3bFv0d/gbh8FJSvbWl4jzP6PdXB11w4tXfHfTb6Xj8X86Qq5KqJH2Dm665yaRyHV+x9v6j2JUA9Ba2BaSZMhZd+kZTbf2+GcbB0UiodqZAW5YKUkY4nTugRoGbbjca24M0hu022yUErADjHJNWzdE4WGNr1cF6VTV4Ky5mJ/ACpRKsNLxJkl5KJKDXjpO/ny/B55IHNWCJnxkSyusMvH2J/072LURQZio5+GsWH48MzhS6HXXVqsv7vXZHm/P6XsMX43aL8wxlBGLebFWb8kI5yLP1DFxF8w4G+AJXNrksE9nddO+nFWvy4C4eNbjm3wibL77bkZ7RemvErbKylNj+qaSNjHRMcBtuNe7Tg+SGhALqPOcdY0w8tIZ9lUbrQak4sjX2v+Evx3e8tuRbJmKJRPMEV72cMVcYzxXbW+LGicrxtEgER3M87AfB30uc82w9JrHnoH0U1Jlnh/gB+4Lm8eiaERoIB70He+8SP/bMmy+K9nUH2VxwZBwC+Zz9cLesz3IJieW4piA== + template: + metadata: + creationTimestamp: null + name: aws-secret + namespace: infrastructure diff --git a/aws-eu1/infrastructure/templates/buckets.yaml b/aws-eu1/infrastructure/templates/buckets.yaml new file mode 100644 index 0000000..1954192 --- /dev/null +++ b/aws-eu1/infrastructure/templates/buckets.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: s3.aws.upbound.io/v1beta1 +kind: Bucket +metadata: + name: crossplane-bucket-{{ uuidv4 }} +spec: + forProvider: + region: us-east-2 + providerConfigRef: + name: default diff --git a/aws-eu1/infrastructure/templates/provider-config.yaml b/aws-eu1/infrastructure/templates/provider-config.yaml new file mode 100644 index 0000000..60d5486 --- /dev/null +++ b/aws-eu1/infrastructure/templates/provider-config.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: aws.upbound.io/v1beta1 +kind: ProviderConfig +metadata: + name: default + annotations: + argocd.argoproj.io/sync-wave: {{ .Values.argoSyncWaves.configs | quote }} +spec: + credentials: + source: Secret + secretRef: + namespace: {{ .Release.Namespace }} + name: aws-secret + key: creds diff --git a/aws-eu1/infrastructure/values.yaml b/aws-eu1/infrastructure/values.yaml new file mode 100644 index 0000000..02df2bf --- /dev/null +++ b/aws-eu1/infrastructure/values.yaml @@ -0,0 +1,3 @@ +argoSyncWaves: + secrets: "-1000" + configs: "-990" \ No newline at end of file diff --git a/foundation/Chart.yaml b/foundation/Chart.yaml new file mode 100644 index 0000000..43fa5cc --- /dev/null +++ b/foundation/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +name: foundation +description: A foundation Helm chart for a cluster + +type: application +version: 0.1.0 +appVersion: "1.20.0" +icon: https://docs.crossplane.io/favicon-32x32.png + +dependencies: + - name: crossplane + version: "1.20.0" + repository: https://charts.crossplane.io/stable + - name: argo-cd + version: "8.0.16" + repository: https://argoproj.github.io/argo-helm + - name: sealed-secrets + version: "2.17.2" + repository: https://bitnami-labs.github.io/sealed-secrets diff --git a/foundation/templates/aws-eu1-apps.yaml b/foundation/templates/aws-eu1-apps.yaml new file mode 100644 index 0000000..ca368d0 --- /dev/null +++ b/foundation/templates/aws-eu1-apps.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: aws-eu1 + annotations: + helm.sh/hook: post-install + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/diegopso/crossplane-example.git + path: aws-eu1/apps + targetRevision: cluster-creation + destination: + server: https://kubernetes.default.svc + namespace: {{ .Release.Namespace }} + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/foundation/templates/providers.yaml b/foundation/templates/providers.yaml new file mode 100644 index 0000000..366b50d --- /dev/null +++ b/foundation/templates/providers.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: pkg.crossplane.io/v1 +kind: Provider +metadata: + name: provider-aws-s3 + annotations: + helm.sh/hook: post-install +spec: + package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.21.1 diff --git a/foundation/templates/sealed-secrets.yaml b/foundation/templates/sealed-secrets.yaml new file mode 100644 index 0000000..18f5ca6 --- /dev/null +++ b/foundation/templates/sealed-secrets.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: sealed-secrets-initial-key + annotations: + helm.sh/hook: pre-install # for install when only Helm is present + argocd.argoproj.io/hook: PreSync # for later syncs when ArgoCD is in controll + labels: + sealedsecrets.bitnami.com/sealed-secrets-key: active +type: kubernetes.io/tls +data: + tls.crt: {{ .Files.Get "tls.crt" | b64enc }} + tls.key: {{ .Files.Get "tls.pem" | b64enc }} \ No newline at end of file diff --git a/foundation/tls.crt b/foundation/tls.crt new file mode 100644 index 0000000..ef196ef --- /dev/null +++ b/foundation/tls.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEzDCCArSgAwIBAgIQA6lbx4ttfvqGnEfRUVkWjzANBgkqhkiG9w0BAQsFADAA +MB4XDTI1MDYwOTE0NTAyMVoXDTM1MDYwNzE0NTAyMVowADCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKdwNt/l5MHnHihkjkMvlrzxbKeEc7wZTM2u6Xje +RMTPF3+lvbTUbX+RIKF5TSc3zH/EA6r99m3zeRwqOpEBn8053c9SzXZbGtoCAJw9 +kvlnW+SRm9vbE6pceHFQkezUOW1S+sef3t7k7wbm323JikPdx/OAQCS9fdoVc5oc +ZlJXEca9iSqqgOK0jkP0tScuQK3EqHN3r8ZWwqMbkeTAFTo2XZ/NSxXYIY6qcHG+ +hmg5B3UKe9lc8Ew0KbvXMS2SMyOXyCikv3C9T1JSNT3h2Uh6O35pK2CeBs2tqwjr +I8qrIhLi+hbkLVk34N0oKX4X8icKXoSaPrKR3wFnEykWiJEjUoiqdm966qXIG/uc +/UFwIZtwVyeVMPjmxHsihGgB79BT3lYL+5R/JOLfzDhpm0JnFTvsIL8CovOY9J1H +nCXC5C6O1VQd2aReZdNaPvHVTnzh+cFFkpB32J4rmMUo2nFpOngE+/Fgjsu7801T +0sjl+3NwhK1sSbDu3PB1gXJBGRMhexcWBFIcCcKuo7SMDkRTXch14COCoeHVjv/k +sR6GjzJ/Su//1mFTQwRX8YLF+qBe+0jV4Q/1SpF9qxmLD0eqsx+0d6+hiW5Tu8tL +Ww5j5povmf9lmQyfaZMs52xO145+p8yPdB31APY+pCzQqSn9c48VfmY4OcdIegYs +ir9LAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIAATAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBRLBiJf/MLhjsgrSRiGoqtLiNrr4DANBgkqhkiG9w0BAQsFAAOCAgEA +S6jCqd6eznHz8R0DvQLtxwG+VaU6c64JHJFD9iGJjrwdvSs8oOM6O2DBMmd3o9Wj +8xpKD/kStE4up8aw+gVGIvo/0W8dCWTEeY3kN2K5F2/hjaSrbUzg+7MiKXM9FqpS +9/F5YlRUv71wiGf2nOF8GUZVve3gFGRQ/sXDe3SC0/MRGpS8+K2cwuDNtfSijInv +5So0kHCvr8dDz4ChpMqQDGDOPIgQ+Rnx7Z3WeorwwNmOHAIf30wxXBLidoASLPzQ +cmufZKoDt178l73kI791og3g1+pc9TcOe2ZnGclxl0eq7WQzsAa4bD62zmlt3ejE +kbXhZc+ELDwdtfxLfeUM6L6l33TsqRH2SrKkHw+yViIPvW4QGJtJzX4NFHN+NHEZ +OtOYd6/F4yS9hoOzA3Cer7H44DmwZ+ltikTKl7r3jUd7Qetf9sNZgsL7SXicq/4h +Ygx171jVyzydCEvz8c3aPNoNBwPQPa8V3NdZ7OpNrmYoHe4n0DwZwiyKxDKFUk9k +LN7ARII3WT7HLLGI2kD9FWRq0sjBteSdvx9jc43UcCHmurKKA/+327mETU3E+Nug +Cz6ilzGovgw/fM6h07vLavv3AAd6woTR1UJ3gfHu9ptawJj1mosjLCQJBkXWmWso +cJ81syS41FPMfM00LpeasyurEVzOZFmqPzkY69+ix88= +-----END CERTIFICATE-----