diff --git a/.github/workflows/kubernetes-tests.yml b/.github/workflows/kubernetes-tests.yml index a937734bb0c..84226b67f7b 100644 --- a/.github/workflows/kubernetes-tests.yml +++ b/.github/workflows/kubernetes-tests.yml @@ -240,4 +240,25 @@ jobs: - name: Tenant KES run: | "${GITHUB_WORKSPACE}/testing/console-tenant+kes.sh" - + + test-policy-binding: + runs-on: ${{ matrix.os }} + needs: + - logsearch + - operator + - lint + - gotest + - getdeps + - govet + strategy: + matrix: + go-version: [ 1.20.x ] + os: [ ubuntu-latest ] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + - name: Test PolicyBinding CRD and sts call on kind + run: | + "${GITHUB_WORKSPACE}/testing/test-policy-binding.sh" diff --git a/.github/workflows/shellcheck.yaml b/.github/workflows/shellcheck.yaml index a0262ff2fe5..9d69be42d68 100644 --- a/.github/workflows/shellcheck.yaml +++ b/.github/workflows/shellcheck.yaml @@ -17,7 +17,7 @@ jobs: - name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: -e SC2046 -e SC1091 -e SC2086 -e SC2090 -e SC2089 -e SC2006 + SHELLCHECK_OPTS: -e SC2046 -e SC1091 -e SC2086 -e SC2090 -e SC2089 -e SC2006 -e SC2206 - name: Run nancy vulnerability report run: | nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///") diff --git a/Makefile b/Makefile index c15d4d53b16..dc22d809f31 100644 --- a/Makefile +++ b/Makefile @@ -69,10 +69,12 @@ regen-crd: @go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.11.1 @${GOPATH}/bin/controller-gen crd:maxDescLen=0,generateEmbeddedObjectMeta=true paths="./..." output:crd:artifacts:config=$(KUSTOMIZE_CRDS) @sed 's#namespace: minio-operator#namespace: {{ .Release.Namespace }}#g' resources/base/crds/minio.min.io_tenants.yaml > $(HELM_TEMPLATES)/minio.min.io_tenants.yaml + @sed 's#namespace: minio-operator#namespace: {{ .Release.Namespace }}#g' resources/base/crds/sts.min.io_policybindings.yaml > $(HELM_TEMPLATES)/sts.min.io_policybindings.yaml regen-crd-docs: @which crd-ref-docs 1>/dev/null || (echo "Installing crd-ref-docs" && GO111MODULE=on go install -v github.com/elastic/crd-ref-docs@latest) - @${GOPATH}/bin/crd-ref-docs --source-path=./pkg/apis/minio.min.io/v2 --config=docs/templates/config.yaml --renderer=asciidoctor --output-path=docs/crd.adoc --templates-dir=docs/templates/asciidoctor/ + @${GOPATH}/bin/crd-ref-docs --source-path=./pkg/apis/minio.min.io/v2 --config=docs/templates/config.yaml --renderer=asciidoctor --output-path=docs/tenant_crd.adoc --templates-dir=docs/templates/asciidoctor/ + @${GOPATH}/bin/crd-ref-docs --source-path=./pkg/apis/sts.min.io/v1alpha1 --config=docs/templates/config.yaml --renderer=asciidoctor --output-path=docs/policybinding_crd.adoc --templates-dir=docs/templates/asciidoctor/ plugin: regen-crd @echo "Building 'kubectl-minio' binary" diff --git a/README.md b/README.md index 3899894a02c..dd769a930ef 100644 --- a/README.md +++ b/README.md @@ -318,4 +318,5 @@ Use of MinIO Operator is governed by the GNU AGPLv3 or later, found in the [LICE - [Apply PodSecurityPolicy](https://github.com/minio/operator/blob/master/docs/pod-security-policy.md). - [Deploy MinIO Tenant with Console](https://github.com/minio/operator/blob/master/docs/console.md). - [Deploy MinIO Tenant with KES](https://github.com/minio/operator/blob/master/docs/kes.md). -- [Tenant API Documentation](docs/crd.adoc) +- [Tenant API Documentation](docs/tenant_crd.adoc) +- [Policy Binding API Documentation](docs/policybinding_crd.adoc) diff --git a/docs/STS.md b/docs/STS.md new file mode 100644 index 00000000000..bb24f6da431 --- /dev/null +++ b/docs/STS.md @@ -0,0 +1,51 @@ +# MinIO Operator STS + +**Native IAM Authentication for Kubernetes.** + +> ⚠️ This feature is an alpha release and is subject to breaking changes in future releases. + +MinIO Operator offers support +for [Secure Tokens](https://min.io/docs/minio/linux/developers/security-token-service.html?ref=op-gh) (a.k.a. STS) which +are a +form of temporary access credentials for your +MinIO Tenant. In essence, this allows you to control access to your MinIO tenant from your applications without having to +explicitly create credentials for each application; in fact no credentials need to be created at all. + +# Authorization Flow + +For an application to gain access into a MinIO Tenant, a `PolicyBinding` granting explicit access to the application's +[Service Account](https://kubernetes.io/docs/concepts/security/service-accounts/). Authorization is then validated +using the following steps: + +1. AssumeRoleWithWebIdentity call +2. Verify ServiceAccount JWT +3. JWT Validation Result +4. Validate SA has PolicyBinding in the Tenant namespace +5. Get PolicyBinding +6. AssumeRole for application +7. Obtain Temporary Credentials +8. Return Temporary Credentials To App +9. Consume Object Storage + +![STS Diagram](images/sts-diagram.png) + +# Requirements + +## Enabling STS functionality + +At the moment, the STS feature ships `off` by default. To turn it on, switch `OPERATOR_STS_ENABLED` to `on` in +the `minio-operator` deployment. + +## TLS + +The STS functionality works only with TLS configured. We can request certificates automatically, but additionally you can +use `cert-manager` or bring your own certificates. + +## SDK support + +Your application must use an SDK that supports `AssumeRole` like behavior. + +# Examples + +We have provided example usage in the [examples/kustomization/sts-example](../examples/kustomization/sts-example) +folder. diff --git a/docs/console.md b/docs/console.md index 2856c3ed0b5..c2edf5c229a 100644 --- a/docs/console.md +++ b/docs/console.md @@ -20,4 +20,4 @@ kubectl create -f https://raw.githubusercontent.com/minio/operator/master/exampl kubectl minio tenant create --name tenant1 --secret tenant1-secret --servers 4 --volumes 16 --capacity 16Ti --namespace tenant1-ns --console-secret console-secret ``` -A complete list of values is available [here](crd.adoc##consoleconfiguration) in the API reference. +A complete list of values is available [here](tenant_crd.adoc##consoleconfiguration) in the API reference. diff --git a/docs/images/sts-diagram.png b/docs/images/sts-diagram.png new file mode 100644 index 00000000000..06736b3cf65 Binary files /dev/null and b/docs/images/sts-diagram.png differ diff --git a/docs/kes.md b/docs/kes.md index b5be3d18ba9..ff84096abc0 100644 --- a/docs/kes.md +++ b/docs/kes.md @@ -36,4 +36,4 @@ KES Configuration is a part of Tenant yaml file. Check the sample file [availabl | spec.kes.kesSecret | Secret to specify KES Configuration. This is a mandatory field. | | spec.kes.metadata | This allows a way to map metadata to the KES pods. Internally `metadata` is a struct type as [explained here](https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#ObjectMeta). | -A complete list of values is available [here](crd.adoc#kesconfig) in the API reference. +A complete list of values is available [here](tenant_crd.adoc#kesconfig) in the API reference. diff --git a/docs/operator-fields.md b/docs/operator-fields.md index 6d9d38e6796..9bca5b20a80 100644 --- a/docs/operator-fields.md +++ b/docs/operator-fields.md @@ -45,4 +45,4 @@ MinIO Operator creates native Kubernetes resources within the cluster. If the Te | spec.pools.volumesPerServer | Set the number of volume mounts per MinIO node. For example if you set `spec.pools[0].Servers = 4`, `spec.pools[1].Servers = 8` and `spec.volumesPerServer = 4`, then you'll have total 12 MinIO Pods, with 4 volume mounts on each Pod. Note that `volumesPerServer` is static per cluster and that expanding a cluster will add new nodes. | | spec.pools.tolerations | Define a toleration for the Tenant pod to match a taint. Refer [this document](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) for details. | -A complete list of values is available [here](crd.adoc) in the API reference. +A complete list of values is available [here](tenant_crd.adoc) in the API reference. diff --git a/docs/policybinding_crd.adoc b/docs/policybinding_crd.adoc new file mode 100644 index 00000000000..57a6422e1d5 --- /dev/null +++ b/docs/policybinding_crd.adoc @@ -0,0 +1,120 @@ +// Generated documentation. Please do not edit. +:anchor_prefix: k8s-api + +[id="{p}-api-reference"] +== API Reference + +:minio-image: https://hub.docker.com/r/minio/minio/tags[minio/minio:RELEASE.2023-01-12T02-06-16Z] +:kes-image: https://hub.docker.com/r/minio/kes/tags[minio/kes:v0.18.0] +:prometheus-image: https://quay.io/prometheus/prometheus:latest[prometheus/prometheus:latest] +:logsearch-image: https://hub.docker.com/r/minio/operator/tags[minio/operator:v4.5.8] +:postgres-image: https://github.com/docker-library/postgres[library/postgres] + + +[id="{anchor_prefix}-sts-min-io-v1alpha1"] +=== sts.min.io/v1alpha1 + +Package v1alpha1 - The following parameters are specific to the `sts.min.io/v1alpha1` MinIO Policy Binding CRD API +PolicyBinding is an Authorization mechanism managed by the Minio Operator. +Using Kubernetes ServiceAccount JSON Web Tokens the binding allow a ServiceAccount to assume temporary IAM credentials. +For more complete documentation on this object, see the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#minio-operator-yaml-reference[MinIO Kubernetes Documentation]. +PolicyBinding is added as part of the MinIO Operator v5.0.0. + + + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-application"] +==== Application + +Application defines the `Namespace` and `ServiceAccount` to authorize the usage of the policies listed + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybindingspec[$$PolicyBindingSpec$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`namespace`* __string__ +|*Required* + + +|*`serviceaccount`* __string__ +|*Required* + + +|=== + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybinding"] +==== PolicyBinding + +PolicyBinding is a https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/[Kubernetes object] describing a MinIO PolicyBinding. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybindinglist[$$PolicyBindingList$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#objectmeta-v1-meta[$$ObjectMeta$$]__ +|Refer to Kubernetes API documentation for fields of `metadata`. + + +|*`spec`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybindingspec[$$PolicyBindingSpec$$]__ +|*Required* + + The root field for the MinIO PolicyBinding object. + +|=== + + + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybindingspec"] +==== PolicyBindingSpec + +PolicyBindingSpec (`spec`) defines the configuration of a MinIO PolicyBinding object. + + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybinding[$$PolicyBinding$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`application`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-application[$$Application$$]__ +|*Required* + + The Application Property identifies the namespace and service account that will be authorized + +|*`policies`* __string array__ +|*Required* + + +|=== + + + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybindingusage"] +==== PolicyBindingUsage + +PolicyBindingUsage are metrics regarding the usage of the policyBinding + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-sts-min-io-v1alpha1-policybindingstatus[$$PolicyBindingStatus$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`authotizations`* __integer__ +| + +|=== + + diff --git a/docs/sidecars.md b/docs/sidecars.md index 433c99933a9..e2f094fe8c2 100644 --- a/docs/sidecars.md +++ b/docs/sidecars.md @@ -32,4 +32,4 @@ The following example configures a warp container to run in the same pod as the **Note:** the MinIO Service for the tenant won't expose the ports added in the sidecar. It's up to the user to expose these ports with their own services. -A complete list of values is available [here](crd.adoc##sidecars) in the API reference. \ No newline at end of file +A complete list of values is available [here](tenant_crd.adoc##sidecars) in the API reference. \ No newline at end of file diff --git a/docs/crd.adoc b/docs/tenant_crd.adoc similarity index 84% rename from docs/crd.adoc rename to docs/tenant_crd.adoc index e4c4d368ded..f4008515fc6 100644 --- a/docs/crd.adoc +++ b/docs/tenant_crd.adoc @@ -14,11 +14,15 @@ [id="{anchor_prefix}-minio-min-io-v2"] === minio.min.io/v2 -Package v2 - This page provides a quick automatically generated reference for the MinIO Operator `minio.min.io/v2` CRD. -For more complete documentation on the MinIO Operator CRD, see https://docs.min.io/minio/k8s/reference/minio-operator-reference[MinIO Kubernetes Documentation]. + +Package v2 - This page provides a quick automatically generated reference for the MinIO Operator `minio.min.io/v2` CRD. For more complete documentation on the MinIO Operator CRD, see https://min.io/docs/minio/kubernetes/upstream/index.html[MinIO Kubernetes Documentation]. + + +The `minio.min.io/v2` API was released with the v4.0.0 MinIO Operator. The MinIO Operator automatically converts existing tenants using the `/v1` API to `/v2`. + + + +.Resource Types +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tenant[$$Tenant$$] + -The `minio.min.io/v2` API was released with the v4.0.0 MinIO Operator. -The MinIO Operator automatically converts existing tenants using the `/v1` API to `/v2`. + [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-auditconfig"] ==== AuditConfig @@ -30,19 +34,19 @@ AuditConfig defines configuration parameters for Audit (type) logs - xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-logconfig[$$LogConfig$$] **** -[cols="25a,75a",options="header"] +[cols="25a,75a", options="header"] |=== | Field | Description -|*`diskCapacityGB`* __integer__ +|*`diskCapacityGB`* __integer__ |*Required* + -Specify the amount of storage to request in Gigabytes (GB) for storing audit logs. + Specify the amount of storage to request in Gigabytes (GB) for storing audit logs. |=== [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-bucket"] -==== Bucket +==== Bucket Bucket describes the default created buckets @@ -55,13 +59,13 @@ Bucket describes the default created buckets |=== | Field | Description -|*`name`* __string__ +|*`name`* __string__ | -|*`region`* __string__ +|*`region`* __string__ | -|*`objectLock`* __boolean__ +|*`objectLock`* __boolean__ | |=== @@ -70,19 +74,18 @@ Bucket describes the default created buckets [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-certificateconfig"] ==== CertificateConfig -CertificateConfig (`certConfig`) defines controlling attributes associated to any TLS certificate automatically generated by the Operator as part of tenant creation. -These fields have no effect if `spec.autoCert: false`. +CertificateConfig (`certConfig`) defines controlling attributes associated to any TLS certificate automatically generated by the Operator as part of tenant creation. These fields have no effect if `spec.autoCert: false`. .Appears In: **** - xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tenantspec[$$TenantSpec$$] **** -[cols="25a,75a",options="header"] +[cols="25a,75a", options="header"] |=== | Field | Description -|*`commonName`* __string__ +|*`commonName`* __string__ |*Optional* + The `CommonName` or `CN` attribute to associate to automatically generated TLS certificates. + @@ -114,6 +117,50 @@ CertificateStatus keeps track of all the certificates managed by the operator |*`autoCertEnabled`* __boolean__ |AutoCertEnabled registers whether we know if the tenant has autocert enabled +|*`customCertificates`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificates[$$CustomCertificates$$]__ +|Provides the output of the `client`, `minio`, and`minioCAs` custom TLS certificates manually added to the Operator. + +|=== + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificateconfig"] +==== CustomCertificateConfig + +CustomCertificateConfig (`customCertificateConfig`) provides attributes associated of the TLS certificates manually added to the Operator as part of tenant creation. These fields contain no data if there are no custom TLS certificates. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificates[$$CustomCertificates$$] +**** + + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificates"] +==== CustomCertificates + +CustomCertificates (`customCertificates`) provides groupings of the TLS certificates manually added to the Operator as part of tenant creation. These fields contain no data if there are no custom TLS certificates. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-certificatestatus[$$CertificateStatus$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`client`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificateconfig[$$CustomCertificateConfig$$] array__ +|*Optional* + + Client + +|*`minio`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificateconfig[$$CustomCertificateConfig$$] array__ +|*Optional* + + Minio + +|*`minioCAs`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-customcertificateconfig[$$CustomCertificateConfig$$] array__ +|*Optional* + + Certificate Authorities + |=== @@ -225,7 +272,7 @@ KESConfig (`kes`) defines the configuration of the https://github.com/minio/kes[ Specify an object containing the following fields: + * - `name` - The name of the Kubernetes secret containing the TLS certificate. + * - `type` - Specify `kubernetes.io/tls` + - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#transport-layer-encryption-tls[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html#procedure-command-line[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. |*`clientCertSecret`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-localcertificatereference[$$LocalCertificateReference$$]__ |*Optional* + @@ -276,6 +323,10 @@ KESConfig (`kes`) defines the configuration of the https://github.com/minio/kes[ * `runAsUser` + * `seLinuxOptions` + +|*`env`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvar-v1-core[$$EnvVar$$] array__ +|*Optional* + + If provided, the MinIO Operator adds the specified environment variables when deploying the KES resource. + |=== @@ -375,6 +426,10 @@ LogConfig (`log`) defines the configuration of the MinIO Log Search API deployed |*Optional* + The https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Service Account] to use for running MinIO KES pods created as part of the Tenant. + +|*`env`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvar-v1-core[$$EnvVar$$] array__ +|*Optional* + + If provided, the MinIO Operator adds the specified environment variables when deploying the Log Search API resource. + |=== @@ -448,6 +503,10 @@ LogDbConfig (`db`) defines the configuration of the PostgreSQL StatefulSet deplo |*Optional* + The https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Service Account] to use for running MinIO KES pods created as part of the Tenant. + +|*`env`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvar-v1-core[$$EnvVar$$] array__ +|*Optional* + + If provided, the MinIO Operator adds the specified environment variables when deploying the Postgres resource. + |=== @@ -481,7 +540,7 @@ Logging describes Logging for MinIO tenants. ==== Pool Pool (`pools`) defines a MinIO server pool on a Tenant. Each pool consists of a set of MinIO server pods which "pool" their storage resources for supporting object storage and retrieval requests. Each server pool is independent of all others and supports horizontal scaling of available storage resources in the MinIO Tenant. + - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#server-pools[MinIO Operator CRD] reference for the `pools` object for examples and more complete documentation. + + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html#procedure-command-line[MinIO Operator CRD] reference for the `pools` object for examples and more complete documentation. + .Appears In: **** @@ -538,8 +597,13 @@ Pool (`pools`) defines a MinIO server pool on a Tenant. Each pool consists of a * `fsGroupChangePolicy` + * `runAsGroup` + * `runAsNonRoot` + - * `runAsUser` + - * `seLinuxOptions` + + * `runAsUser` + + +|*`containerSecurityContext`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#securitycontext-v1-core[$$SecurityContext$$]__ +|Specify the https://kubernetes.io/docs/tasks/configure-pod-container/security-context/[Security Context] of containers in the pool. The Operator supports only the following container security fields: + + * `runAsGroup` + + * `runAsNonRoot` + + * `runAsUser` + |*`annotations`* __object (keys:string, values:string)__ |*Optional* + @@ -550,6 +614,10 @@ Pool (`pools`) defines a MinIO server pool on a Tenant. Each pool consists of a |*Optional* + If provided, use these labels for the Pool Objects Meta annotations (Statefulset and Pod template) +|*`runtimeClassName`* __string__ +|*Optional* + + If provided, each pod on the Statefulset will run with the specified RuntimeClassName, for more info https://kubernetes.io/docs/concepts/containers/runtime-class/ + |=== @@ -647,6 +715,10 @@ PrometheusConfig (`prometheus`) defines the configuration of a Prometheus instan |*Optional* + Specify node affinity, pod affinity, and pod anti-affinity for the Prometheus pods. + +|*`tolerations`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#toleration-v1-core[$$Toleration$$] array__ +|*Optional* + + Specify one or more https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/[Kubernetes tolerations] to apply to the Prometheus pods. + |*`topologySpreadConstraints`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#topologyspreadconstraint-v1-core[$$TopologySpreadConstraint$$] array__ |*Optional* + Specify one or more https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/[Kubernetes Topology Spread Constraints] to apply to pods deployed in the MinIO pool. @@ -669,6 +741,10 @@ PrometheusConfig (`prometheus`) defines the configuration of a Prometheus instan |*Optional* + The https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Service Account] to use for running MinIO KES pods created as part of the Tenant. + +|*`env`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvar-v1-core[$$EnvVar$$] array__ +|*Optional* + + If provided, the MinIO Operator adds the specified environment variables when deploying the Prometheus resource. + |=== @@ -769,6 +845,12 @@ Tenant is a https://kubernetes.io/docs/concepts/overview/working-with-objects/ku |=== | Field | Description +|*`apiVersion`* __string__ +|`minio.min.io/v2` + +|*`kind`* __string__ +|`Tenant` + |*`metadata`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#objectmeta-v1-meta[$$ObjectMeta$$]__ |Refer to Kubernetes API documentation for fields of `metadata`. @@ -786,7 +868,7 @@ Tenant is a https://kubernetes.io/docs/concepts/overview/working-with-objects/ku [id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tenantdomains"] ==== TenantDomains -TenantDomains (`domains`) - List of domains used to access the tenant from outside the kubernetes clusters. this will only configure MinIO for the domains listed, but external DNS configuration is still needed. +TenantDomains (`domains`) - List of domains used to access the tenant from outside the kubernetes clusters. this will only configure MinIO for the domains listed, but external DNS configuration is still needed. The listed domains should include schema and port if any is used, i.e. https://minio.domain.com:8123 .Appears In: **** @@ -800,6 +882,9 @@ TenantDomains (`domains`) - List of domains used to access the tenant from outsi |*`minio`* __string array__ |List of Domains used by MinIO. This will enable DNS style access to the object store where the bucket name is inferred from a subdomain in the domain. +|*`console`* __string__ +|Domain used to expose the MinIO Console, this will configure the redirect on MinIO when visiting from the browser If Console is exposed via a subpath, the domain should include it, i.e. https://console.domain.com:8123/subpath/ + |=== @@ -831,7 +916,7 @@ TenantScheduler (`scheduler`) - Object describing Kubernetes Scheduler to use fo TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + The following parameters are specific to the `minio.min.io/v2` MinIO CRD API `spec` definition added as part of the MinIO Operator v4.0.0. + - For more complete documentation on this object, see the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#minio-operator-yaml-reference[MinIO Kubernetes Documentation]. + + For more complete documentation on this object, see the https://min.io/docs/minio/kubernetes/upstream/operations/installation.html[MinIO Kubernetes Documentation]. + .Appears In: **** @@ -846,7 +931,7 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + |*Required* + An array of objects describing each MinIO server pool deployed in the MinIO Tenant. Each pool consists of a set of MinIO server pods which "pool" their storage resources for supporting object storage and retrieval requests. Each server pool is independent of all others and supports horizontal scaling of available storage resources in the MinIO Tenant. + The MinIO Tenant `spec` *must have* at least *one* element in the `pools` array. + - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#server-pools[MinIO Operator CRD] reference for the `pools` object for examples and more complete documentation. + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html[MinIO Operator CRD] reference for the `pools` object for examples and more complete documentation. |*`image`* __string__ |*Optional* + @@ -861,7 +946,7 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + Pod Management Policy for pod created by StatefulSet |*`credsSecret`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#localobjectreference-v1-core[$$LocalObjectReference$$]__ -|*Required* + +|*optional* + Specify a https://kubernetes.io/docs/concepts/configuration/secret/[Kubernetes opaque secret] to use for setting the MinIO root access key and secret key. Specify the secret as `name: `. The Kubernetes secret must contain the following fields: + * `data.accesskey` - The access key for the root credentials + * `data.secretkey` - The secret key for the root credentials + @@ -877,7 +962,7 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + Each element in the `externalCertSecret` array is an object containing the following fields: + * - `name` - The name of the Kubernetes secret containing the TLS certificate. + * - `type` - Specify `kubernetes.io/tls` + - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#transport-layer-encryption-tls[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html#create-tenant-security-section[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. |*`externalCaCertSecret`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-localcertificatereference[$$LocalCertificateReference$$] array__ |*Optional* + @@ -886,7 +971,7 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + Each element in the `externalCertSecret` array is an object containing the following fields: + * - `name` - The name of the Kubernetes secret containing the Certificate Authority. + * - `type` - Specify `kubernetes.io/tls`. + - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#transport-layer-encryption-tls[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html#create-tenant-security-section[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. |*`externalClientCertSecret`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-localcertificatereference[$$LocalCertificateReference$$]__ |*Optional* + @@ -896,7 +981,15 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + * `type` - Specify `kubernetes.io/tls` + The specified certificate *must* correspond to an identity on the KES server. See the https://github.com/minio/kes/wiki/Configuration#policy-configuration[KES Wiki] for more information on KES identities. + If deploying KES with the MinIO Operator, include the hash of the certificate as part of the <> object specification. + - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#transport-layer-encryption-tls[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html#create-tenant-security-section[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. + +|*`externalClientCertSecrets`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-localcertificatereference[$$LocalCertificateReference$$] array__ +|*Optional* + + Provide support for mounting additional client certificate into MinIO Tenant pods Multiple client certificates will be mounted using the following folder structure: + certs | | + client.crt | + client.key | + client.crt | + client.key | + client.crt | + client.key + Specify a https://kubernetes.io/docs/concepts/configuration/secret/[Kubernetes TLS secrets]. The MinIO Operator copies the specified certificate to every MinIO server pod in the tenant that later can be referenced using environment variables. The secret *must* contain the following fields: + + * `name` - The name of the Kubernetes secret containing the TLS certificate. + + * `type` - Specify `kubernetes.io/tls` + |*`mountPath`* __string__ |*Optional* + @@ -912,7 +1005,7 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + * Specify `true` to explicitly enable automatic certificate generate (Default). + * Specify `false` to disable automatic certificate generation. + If `requestAutoCert` is set to `false` *and* `externalCertSecret` is omitted, the MinIO Tenant deploys *without* TLS enabled. - See the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#transport-layer-encryption-tls[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. + See the https://min.io/docs/minio/kubernetes/upstream/operations/install-deploy-manage/deploy-minio-tenant.html#create-tenant-security-section[MinIO Operator CRD] reference for examples and more complete documentation on configuring TLS for MinIO Tenants. |*`liveness`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#probe-v1-core[$$Probe$$]__ |Liveness Probe for container liveness. Container will be restarted if the probe fails. @@ -920,6 +1013,9 @@ TenantSpec (`spec`) defines the configuration of a MinIO Tenant object. + |*`readiness`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#probe-v1-core[$$Probe$$]__ |Readiness Probe for container readiness. Container will be removed from service endpoints if the probe fails. +|*`startup`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#probe-v1-core[$$Probe$$]__ +|Startup Probe allows to configure a max grace period for a pod to start before getting traffic routed to it. + |*`s3`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-s3features[$$S3Features$$]__ |*Optional* + *Deprecated in Operator v4.3.2* + S3 related features can be disabled or enabled such as `bucketDNS` etc. @@ -1031,6 +1127,35 @@ TenantUsage are metrics regarding the usage and capacity of the tenant |*`rawUsage`* __integer__ |Usage is the raw usage on disks in bytes. +|*`tiers`* __xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tierusage[$$TierUsage$$] array__ +|Tiers includes the usage of individual tiers in the tenant + +|=== + + +[id="{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tierusage"] +==== TierUsage + +TierUsage represents the usage from a tier setup by the tenant + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-minio-operator-pkg-apis-minio-min-io-v2-tenantusage[$$TenantUsage$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description + +|*`Name`* __string__ +|Name of the tier + +|*`Type`* __string__ +|type of the tier + +|*`totalSize`* __integer__ +|TotalSize usage of the tier + |=== diff --git a/examples/kustomization/sts-example/README.md b/examples/kustomization/sts-example/README.md new file mode 100644 index 00000000000..d45852a75e6 --- /dev/null +++ b/examples/kustomization/sts-example/README.md @@ -0,0 +1,117 @@ +# MinIO Operator STS: Native IAM Authentication for Kubernetes + +Each example in this folder contains an example using a different SDK on how to adopt Operator's STS. + +> ⚠️ This feature is an alpha release and is subject to breaking changes in future releases. + +# Requirements + +## Enabling STS functionality + +At the moment, the STS feature ships off by default, to turn it on switch `OPERATOR_STS_ENABLED` to `on` on +the `minio-operator` deployment. + +## TLS + +The STS functionality works only with TLS configured. We can request certificates automatically, but additional you can +use `cert-manager` or bring your own certificates. + +# Installation + +To install the example, you need an existing tenant, optionally, you can install the `tenant-lite` example, or +the `tenant-certmanager` example + +# 0. Enable STS Functionality + +If you haven't done so, enable the STS feature on operator by turning setting the feature flag `OPERATOR_STS_ENABLED=on` + +```shell +kubectl -n minio-operator set env deployment/minio-operator OPERATOR_STS_ENABLED=on +``` + +# 1. Install Tenant (Optional) + +```shell +kubectl apply -k examples/kustomization/sts-example/tenant +``` + +For an example with Cert Manager + +```shell +kubectl apply -k examples/kustomization/sts-example/tenant-certmanager +``` + +# 2. Create a bucket and a policy (Optional) + +We will set up some sample buckets to access from our sample application + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-data +``` + +# 3. Install sample application + +The sample application will install to `sts-client` namespace and grant access to the job called `sts-example-job` to +access `tenant` with the MinIO Policy called `test-bucket-rw` that we created in the previous step on +namespace `minio-tenant-1` by installing a `PolicyBinding` on the `minio-tenant-1` namespace. + +Example policy binding (see CRD documentation in [policybinding_crd.adoc](../../../docs/policybinding_crd.adoc) ) + +```yaml +apiVersion: sts.min.io/v1alpha1 +kind: PolicyBinding +metadata: + name: binding-1 + namespace: minio-tenant-1 +spec: + application: + namespace: sts-client + serviceaccount: stsclient-sa + policies: + - test-bucket-rw + +``` + +To install the sample application, which uses the Go SDK, run: + +```shell +kubectl apply -k examples/kustomization/sts-example/ +``` + +To use a specfic SDK, use any of the following: + +### Go + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-clients/minio-sdk/go +``` + +### Java + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-clients/minio-sdk/java +``` + +### Python + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-clients/minio-sdk/python +``` + +### Python: AWS Boto3 SDK + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-clients/aws-sdk/python +``` + +### Javascript + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-clients/minio-sdk/javascript +``` + +### .NET + +```shell +kubectl apply -k examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet +``` \ No newline at end of file diff --git a/examples/kustomization/sts-example/kustomization.yaml b/examples/kustomization/sts-example/kustomization.yaml new file mode 100644 index 00000000000..084fea48ea0 --- /dev/null +++ b/examples/kustomization/sts-example/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - sts-app \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/Makefile b/examples/kustomization/sts-example/sample-clients/Makefile new file mode 100644 index 00000000000..d657dcc0e7e --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/Makefile @@ -0,0 +1,28 @@ + +all: build + +build: miniosdkgo miniosdkjava awssdkpython + +miniosdkdotnet: + @cd minio-sdk/dotnet/ && \ + docker build -q -t miniodev/operator-sts-example:minio-sdk-dotnet . + +miniosdkgo: + @cd minio-sdk/go/ && \ + docker build -q -t miniodev/operator-sts-example:minio-sdk-go . + +miniosdkjava: + @cd minio-sdk/java/ && \ + docker build -q -t miniodev/operator-sts-example:minio-sdk-java . + +miniosdkjavascript: + @cd minio-sdk/javascript/ && \ + docker build -q -t miniodev/operator-sts-example:minio-sdk-javascript . + +awssdkpython: + @cd aws-sdk/python/ && \ + docker build -q -t miniodev/operator-sts-example:aws-sdk-python . + +miniosdkpython: + @cd minio-sdk/python/ && \ + docker build -q --platform=linux/amd64 -t miniodev/operator-sts-example:minio-sdk-python . diff --git a/examples/kustomization/sts-example/sample-clients/aws-sdk/python/Dockerfile b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/Dockerfile new file mode 100644 index 00000000000..0d00f80fea3 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/Dockerfile @@ -0,0 +1,12 @@ +FROM ubuntu:latest + +RUN \ + apt-get update && \ + apt-get install -y curl ca-certificates python3 python3-pip + +RUN mkdir app +WORKDIR /app +COPY requirements.txt /app/requirements.txt +RUN pip3 install -r requirements.txt +COPY main.py /app/main.py +CMD ["python3", "/app/main.py"] diff --git a/examples/kustomization/sts-example/sample-clients/aws-sdk/python/kustomization.yaml b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/kustomization.yaml new file mode 100644 index 00000000000..1e155ac60e7 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../sts-app + +images: + - name: miniodev/operator-sts-example + newTag: aws-sdk-python + +patchesJson6902: + - target: + group: batch + version: v1 + kind: Job + name: sts-example-job + patch: | + - op: replace + path: /metadata/name + value: sts-client-example-aws-sdk-python-job \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/aws-sdk/python/main.py b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/main.py new file mode 100644 index 00000000000..1f0da3cd999 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/main.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# This file is part of MinIO Operator +# Copyright (c) 2023 MinIO, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import boto3 +import os +import sys +from urllib.parse import urlparse + +sts_endpoint = os.getenv("STS_ENDPOINT") +tenant_endpoint = os.getenv("MINIO_ENDPOINT") +tenant_namespace = os.getenv("TENANT_NAMESPACE") +token_path = os.getenv("AWS_WEB_IDENTITY_TOKEN_FILE") +bucket = os.getenv("BUCKET") +policy_path = os.getenv("STS_POLICY") + +role_arn = "arn:aws:iam::111111111:dummyroot" +role_session_name = "optional-session-name" +os.environ.setdefault('AWS_ROLE_ARN', role_arn) #In AWS SDK RoleArn parameter is mandatory + +policy = None + +if policy_path is not None: + with open(policy_path, "r") as f: + policy = f.read() + +with open(token_path, "r") as f: + sa_jwt = f.read() + +if sa_jwt == "" or sa_jwt == None: + print("Token is empty") + sys.exit(1) + +stsUrl = urlparse(f"{sts_endpoint}/{tenant_namespace}") + +sts = boto3.client('sts', endpoint_url=stsUrl.geturl(), verify=False) +assumed_role_object = sts.assume_role_with_web_identity( + RoleArn=role_arn, + RoleSessionName=role_session_name, + Policy=policy, + DurationSeconds=25536, + WebIdentityToken=sa_jwt +) + +credentials = assumed_role_object['Credentials'] +print(credentials) + +tenantUrl = urlparse(tenant_endpoint) +s3_client = boto3.resource('s3', + aws_access_key_id=credentials['AccessKeyId'], + aws_secret_access_key=credentials['SecretAccessKey'], + aws_session_token=credentials['SessionToken'], + endpoint_url=tenantUrl.geturl(), verify=False) + +my_bucket = s3_client.Bucket(bucket) +for my_bucket_object in my_bucket.objects.all(): + print(my_bucket_object) diff --git a/examples/kustomization/sts-example/sample-clients/aws-sdk/python/requirements.txt b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/requirements.txt new file mode 100644 index 00000000000..a195582a4e7 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/aws-sdk/python/requirements.txt @@ -0,0 +1 @@ +boto3>=1.24 \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/Dockerfile b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/Dockerfile new file mode 100644 index 00000000000..f14eacca6c7 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:22.10 + +RUN apt-get update && \ + apt-get install -y apt-transport-https \ + dotnet-sdk-7.0 curl ca-certificates + +RUN curl https://packages.microsoft.com/config/ubuntu/22.10/packages-microsoft-prod.deb --output packages-microsoft-prod.deb && \ + dpkg -i packages-microsoft-prod.deb && \ + rm packages-microsoft-prod.deb + +RUN mkdir app +WORKDIR /app +COPY dotnet.csproj /app +COPY Program.cs /app +RUN dotnet publish + +ENTRYPOINT ["/app/bin/Debug/net7.0/dotnet"] diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/Program.cs b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/Program.cs new file mode 100644 index 00000000000..7ecb5891b74 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/Program.cs @@ -0,0 +1,151 @@ +// This file is part of MinIO Operator +// Copyright (c) 2023 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System; +using Minio; +using System.Threading.Tasks; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; + +namespace sts +{ + class Example + { + static void Main(string[] args) + { + var tenantEndpoint = Environment.GetEnvironmentVariable("MINIO_ENDPOINT"); + var stsEndpoint = Environment.GetEnvironmentVariable("STS_ENDPOINT"); + var tenantNamespace = Environment.GetEnvironmentVariable("TENANT_NAMESPACE"); + var bucketName = Environment.GetEnvironmentVariable("BUCKET"); + var kubeRootCAPath = Environment.GetEnvironmentVariable("KUBERNETES_CA_PATH"); + var stsCAPath = Environment.GetEnvironmentVariable("STS_CA_PATH"); + + Environment.SetEnvironmentVariable("AWS_ROLE_ARN","arn:aws:iam::111111111:dummyroot"); + Environment.SetEnvironmentVariable("AWS_ROLE_SESSION_NAME","optional-session-name"); + + string? caFile = ""; + + if (FileExists(stsCAPath)) + { + caFile = stsCAPath; + } + else + { + if (FileExists(kubeRootCAPath)) + { + caFile = kubeRootCAPath; + } + } + + try + { + var tenantEndpointUrl = new Uri(tenantEndpoint); + var credentialsProvider = new Minio.Credentials.IAMAWSProvider(); + using var minioClient = new MinioClient() + .WithEndpoint(tenantEndpointUrl.Host, tenantEndpointUrl.Port) + .WithSSL() + .WithCredentialsProvider(credentialsProvider) + .WithHttpClient(GetHttpTransport(caFile)) + .Build(); + + var url = new Uri($"{stsEndpoint}/{tenantNamespace}"); + credentialsProvider = credentialsProvider + .WithEndpoint(url.ToString) + .WithMinioClient(minioClient); + + credentialsProvider.Validate(); + + var credentials = credentialsProvider.GetCredentials(); + System.Console.WriteLine($"AccessKey: ${credentials.AccessKey}"); + System.Console.WriteLine($"AccessKey: ${credentials.SecretKey}"); + System.Console.WriteLine($"AccessKey: ${credentials.SessionToken}"); + + ListBuckets(minioClient).GetAwaiter().GetResult(); + ListObjects(minioClient, bucketName).GetAwaiter().GetResult(); + } + catch (UriFormatException uer) + { + Console.WriteLine($"STS endpoint malformed: {uer.Message}"); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + Console.WriteLine(ex.StackTrace); + Environment.Exit(111); + } + } + + public static async Task ListBuckets(IMinioClient minio) + { + try + { + Console.WriteLine("Running example for API: ListBucketsAsync"); + var list = await minio.ListBucketsAsync().ConfigureAwait(false); + foreach (var bucket in list.Buckets) Console.WriteLine($"{bucket.Name} {bucket.CreationDateDateTime}"); + Console.WriteLine(); + } + catch (Exception e) + { + Console.WriteLine($"[Bucket] Exception: {e}"); + } + } + + public static async Task ListObjects(IMinioClient minio, string bucketName) + { + try + { + var listArgs = new ListObjectsArgs() + .WithBucket(bucketName) + .WithRecursive(true); + var observable = minio.ListObjectsAsync(listArgs); + var subscription = observable.Subscribe( + item => Console.WriteLine($"Object: {item.Key}"), + ex => Console.WriteLine($"OnError: {ex}"), + () => Console.WriteLine($"Listed all objects in bucket {bucketName}\n")); + } + catch (System.Exception e) + { + Console.WriteLine($"[Object] Exception: {e}"); + } + } + + private static HttpClient GetHttpTransport(string caPath) + { + var handler = new HttpClientHandler(); + if (!string.IsNullOrEmpty(caPath)) + { + handler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) => + { + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + chain.ChainPolicy.CustomTrustStore.Add(new X509Certificate2(caPath)); + return chain.Build(cert); + }; + } + + var httpClient = new HttpClient(handler); + return httpClient; + } + + private static bool FileExists(string? path) + { + if (String.IsNullOrEmpty(path)) + { + return false; + } + return false; + } + } +} diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/dotnet.csproj b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/dotnet.csproj new file mode 100644 index 00000000000..54f0ad7a922 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/dotnet.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/kustomization.yaml b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/kustomization.yaml new file mode 100644 index 00000000000..edcd74be0af --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/dotnet/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../sts-app + +images: + - name: miniodev/operator-sts-example + newTag: minio-sdk-dotnet + +patchesJson6902: + - target: + group: batch + version: v1 + kind: Job + name: sts-example-job + patch: | + - op: replace + path: /metadata/name + value: sts-client-example-minio-sdk-dotnet-job \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/Dockerfile b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/Dockerfile new file mode 100644 index 00000000000..1e6ec52cf03 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/Dockerfile @@ -0,0 +1,25 @@ +FROM golang as golayer + +RUN \ + apt-get update && \ + apt-get install -y curl ca-certificates golang-go + +ADD go.mod /go/src/github.com/minio/operator/sts/example/go/go.mod +ADD go.sum /go/src/github.com/minio/operator/sts/example/go/go.sum +WORKDIR /go/src/github.com/minio/operator/sts/example/go/ + +# Get dependencies - will also be cached if we won't change mod/sum +RUN go mod download + +ADD . /go/src/github.com/minio/operator/sts/example/go/ + +ENV CGO_ENABLED=0 + +RUN go build -o go-example . + +FROM scratch + +COPY --from=golayer /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=golayer /go/src/github.com/minio/operator/sts/example/go/go-example /usr/local/bin/ + +ENTRYPOINT ["go-example"] diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/README.md b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/README.md new file mode 100644 index 00000000000..d36ca9c7d5e --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/README.md @@ -0,0 +1,7 @@ +# Go SDK STS Example + +To build this example run + +```shell +docker build -t minio-sts-go-example . +``` \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/go.mod b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/go.mod new file mode 100644 index 00000000000..f7b6c7d4dcc --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/go.mod @@ -0,0 +1,24 @@ +module github.com/minio/operator/sts/example/go + +go 1.20 + +require github.com/minio/minio-go/v7 v7.0.33 + +require ( + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/cpuid/v2 v2.1.0 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/rs/xid v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect +) diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/go.sum b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/go.sum new file mode 100644 index 00000000000..87de8f2b1db --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/go.sum @@ -0,0 +1,52 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= +github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.33 h1:jLEHTp9jg2zWBa5w9W1i8WXq6o+oGRcjsdk9HbFgdlc= +github.com/minio/minio-go/v7 v7.0.33/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/kustomization.yaml b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/kustomization.yaml new file mode 100644 index 00000000000..d404861a6e2 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../sts-app + +images: + - name: miniodev/operator-sts-example + newTag: minio-sdk-go + +patchesJson6902: + - target: + group: batch + version: v1 + kind: Job + name: sts-example-job + patch: | + - op: replace + path: /metadata/name + value: sts-client-example-minio-sdk-go-job \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go new file mode 100644 index 00000000000..09bf15f2937 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/go/main.go @@ -0,0 +1,160 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package main + +import ( + "context" + "crypto/x509" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "path" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +func main() { + tenantEndpoint := os.Getenv("MINIO_ENDPOINT") + stsEndpoint := os.Getenv("STS_ENDPOINT") + tenantNamespace := os.Getenv("TENANT_NAMESPACE") + bucketName := os.Getenv("BUCKET") + kubeRootCApath := os.Getenv("KUBERNETES_CA_PATH") + + token, err := getToken() + if err != nil { + log.Fatalf("Could not get Service account JWT: %s", err) + panic(1) + } + if token == "" { + log.Fatal("Service account JWT is empty") + panic(1) + } + // Declare a custom transport to trust custom CA's, in this case we will trust + // Kubernete's Internal CA or Cert Manager's CA + httpsTransport, err := getHttpsTransportWithCACert(kubeRootCApath) + if err != nil { + log.Fatalf("Error Creating https transport: %s", err) + panic(1) + } + + stsEndpointURL, err := url.Parse(stsEndpoint) + stsEndpointURL.Path = path.Join(stsEndpointURL.Path, tenantNamespace) + if err != nil { + log.Fatalf("Error parsing sts endpoint: %v", err) + panic(1) + } + + sts := credentials.New(&credentials.IAM{ + Client: &http.Client{ + Transport: httpsTransport, + }, + Endpoint: stsEndpointURL.String(), + }) + + retrievedCredentials, err := sts.Get() + if err != nil { + log.Fatalf("Error retrieving STS credentials: %v", err) + panic(1) + } + fmt.Println("AccessKeyID:", retrievedCredentials.AccessKeyID) + fmt.Println("SecretAccessKey:", retrievedCredentials.SecretAccessKey) + fmt.Println("SessionToken:", retrievedCredentials.SessionToken) + + tenantEndpointURL, err := url.Parse(tenantEndpoint) + if err != nil { + log.Fatalf("Error parsing tenant endpoint: %s", err) + panic(1) + } + + minioClient, err := minio.New(tenantEndpointURL.Host, &minio.Options{ + Creds: sts, + Secure: tenantEndpointURL.Scheme == "https", + Transport: httpsTransport, + }) + + if err != nil { + log.Fatalf("Error initializing client: %v", err) + panic(1) + } + + fmt.Print("List Buckets:") + buckets, err := minioClient.ListBuckets(context.Background()) + if err != nil { + log.Fatalln(err) + } + for _, bucket := range buckets { + log.Println(bucket) + } + + fmt.Printf("List Objects in bucket %s", bucketName) + opts := minio.ListObjectsOptions{ + Prefix: "/", + Recursive: true, + } + for object := range minioClient.ListObjects(context.Background(), bucketName, opts) { + if object.Err != nil { + fmt.Println(object.Err) + panic(1) + } + fmt.Println(object) + } + return +} + +func getToken() (string, error) { + tokenpath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") + fileContent, err := ioutil.ReadFile(tokenpath) + if err != nil { + return "", err + } + return string(fileContent), nil +} + +func getFile(path string) ([]byte, error) { + return ioutil.ReadFile(path) +} + +func getHttpsTransportWithCACert(cacertpath string) (*http.Transport, error) { + caCertificate, err := getFile(cacertpath) + if err != nil { + return nil, fmt.Errorf("Error loading CA Certifiate : %s", err) + } + + transport, err := minio.DefaultTransport(true) + if err != nil { + return nil, fmt.Errorf("Error creating default transport : %s", err) + } + + if transport.TLSClientConfig.RootCAs == nil { + pool, err := x509.SystemCertPool() + if err != nil { + log.Fatalf("Error initializing TLS Pool: %s", err) + transport.TLSClientConfig.RootCAs = x509.NewCertPool() + } else { + transport.TLSClientConfig.RootCAs = pool + } + } + + if ok := transport.TLSClientConfig.RootCAs.AppendCertsFromPEM(caCertificate); !ok { + return nil, fmt.Errorf("Error parsing CA Certifiate : %s", err) + } + return transport, nil +} diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/java/Dockerfile b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/Dockerfile new file mode 100644 index 00000000000..0f8d34a646b --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/Dockerfile @@ -0,0 +1,9 @@ + +FROM openjdk:11 + +RUN mkdir /app +WORKDIR /app + +RUN mvn pacakge + +ENTRYPOINT ["java", "-jar", "./app/target/operator-sts-0.1.0.jar"] diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/java/kustomization.yaml b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/kustomization.yaml new file mode 100644 index 00000000000..f8333bfb2cf --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../sts-app + +images: + - name: miniodev/operator-sts-example + newTag: minio-sdk-java + +patchesJson6902: + - target: + group: batch + version: v1 + kind: Job + name: sts-example-job + patch: | + - op: replace + path: /metadata/name + value: sts-client-example-minio-sdk-java-job \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/java/pom.xml b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/pom.xml new file mode 100644 index 00000000000..659842dc9cc --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.springframework + operator-sts + jar + 0.1.0 + + + 1.8 + 1.8 + + + + io.minio + minio + 8.5.2 + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + main.OperatorSTSExample + + + + + + + + + \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/java/src/main/java/operator/sts/OperatorSTSExample.java b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/src/main/java/operator/sts/OperatorSTSExample.java new file mode 100644 index 00000000000..675903eca4b --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/java/src/main/java/operator/sts/OperatorSTSExample.java @@ -0,0 +1,62 @@ +// This file is part of MinIO Operator +// Copyright (c) 2023 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package sts; +import io.minio.ListObjectsArgs; +import io.minio.MinioClient; +import io.minio.Result; +import io.minio.errors.MinioException; +import io.minio.messages.Item; +import io.minio.credentials.CertificateIdentityProvider; +import io.minio.credentials.Provider; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + + +public class OperatorSTSExample { + public static void main(String[] args) throws Exception{ + try { + String operatorEndpoint = System.getenv("OPERATOR_ENDPOINT"); + String minioEndpoint = System.getenv("TENANT_ENDPOINT"); + String tenantNamespace = System.getenv("TENANT_NAMESPACE"); + String bucketName = System.getenv("BUCKET"); + + SSLSocketFactory sslSocketFactory = null; + X509TrustManager trustManager = null; + + Provider provider = new CertificateIdentityProvider(operatorEndpoint, sslSocketFactory, trustManager, null, null); + + MinioClient minioClient = MinioClient.builder() + .endpoint(minioEndpoint) + .credentialsProvider(provider) + .build(); + + // Lists objects information. + Iterable> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build()); + + for (Result result : results) { + Item item = result.get(); + System.out.println(item.lastModified() + "\t" + item.size() + "\t" + item.objectName()); + } + } catch (MinioException e) { + System.out.println("Error occurred: " + e); + } + } +} \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/javascript/Dockerfile b/examples/kustomization/sts-example/sample-clients/minio-sdk/javascript/Dockerfile new file mode 100644 index 00000000000..3592f01346d --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/javascript/Dockerfile @@ -0,0 +1,14 @@ + +FROM ubuntu:latest + +RUN mkdir /app +WORKDIR /app +COPY client /app/ +COPY start.sh /app/ +RUN chmod +x /app/start.sh + +RUN \ + apt-get update && \ + apt-get install -y curl ca-certificates golang-go + +CMD ["./app/client"] diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/javascript/kustomization.yaml b/examples/kustomization/sts-example/sample-clients/minio-sdk/javascript/kustomization.yaml new file mode 100644 index 00000000000..c8bc87ed1af --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/javascript/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../sts-app + +images: + - name: miniodev/operator-sts-example + newTag: minio-sdk-javascript + +patchesJson6902: + - target: + group: batch + version: v1 + kind: Job + name: sts-example-job + patch: | + - op: replace + path: /metadata/name + value: sts-client-example-minio-sdk-javascript-job \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/python/Dockerfile b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/Dockerfile new file mode 100644 index 00000000000..24a415461fb --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.10-slim + +RUN \ + apt-get update && \ + apt-get install -y curl ca-certificates +RUN mkdir app + +WORKDIR /app + +COPY requirements.txt /app/requirements.txt +RUN pip3 install -r requirements.txt + +COPY main.py /app/main.py + +CMD ["python3", "/app/main.py"] diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/python/kustomization.yaml b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/kustomization.yaml new file mode 100644 index 00000000000..312ecab0c1d --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/kustomization.yaml @@ -0,0 +1,20 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - ../../../sts-app + +images: + - name: miniodev/operator-sts-example + newTag: minio-sdk-python + +patchesJson6902: + - target: + group: batch + version: v1 + kind: Job + name: sts-example-job + patch: | + - op: replace + path: /metadata/name + value: sts-client-example-minio-sdk-python-job \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/python/main.py b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/main.py new file mode 100644 index 00000000000..d2bf20ec2ab --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/main.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# This file is part of MinIO Operator +# Copyright (c) 2023 MinIO, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from minio import Minio +from minio.credentials import IamAwsProvider +from urllib.parse import urlparse +import urllib3 +import os +import sys +# import logging + +sts_endpoint = os.getenv("STS_ENDPOINT") +tenant_endpoint = os.getenv("MINIO_ENDPOINT") +tenant_namespace = os.getenv("TENANT_NAMESPACE") +token_path = os.getenv("AWS_WEB_IDENTITY_TOKEN_FILE") +bucketName = os.getenv("BUCKET") +kubernetes_ca_file = os.getenv("KUBERNETES_CA_PATH") + +# logging.basicConfig(format='%(message)s', level=logging.DEBUG) +# logger = logging.getLogger() +# logger.setLevel(logging.DEBUG) + +with open(token_path, "r") as f: + sa_jwt = f.read() + +if sa_jwt == "" or sa_jwt == None: + print("Token is empty") + sys.exit(1) + +https_transport = urllib3.PoolManager( + cert_reqs='REQUIRED', + ca_certs=kubernetes_ca_file, + retries=urllib3.Retry( + total=5, + backoff_factor=0.2, + status_forcelist=[500, 502, 503, 504], + ) + ) + +stsUrl = urlparse(f"{sts_endpoint}/{tenant_namespace}") +provider = IamAwsProvider(stsUrl.geturl(), http_client=https_transport) + +credentials = provider.retrieve() + +print(f"Access key: {credentials.access_key}") +print(f"Secret key: {credentials.secret_key}") +print(f"Session Token key: {credentials.session_token}") + +tenantUrl = urlparse(tenant_endpoint) +isHttps = (tenantUrl.scheme == "https") + +client = Minio( + f"{tenantUrl.hostname}:{tenantUrl.port}/{tenantUrl.path}", + credentials=provider, + secure=isHttps, + http_client=https_transport + ) + +# list buckets +print("Listing Buckets:") +buckets = client.list_buckets() +for bucket in buckets: + print(bucket.name, bucket.creation_date) + +# list objects in a bucket +print(f"Listing Objects in bucket {bucketName}:") +objects = client.list_objects(bucketName, recursive=True) +for obj in objects: + print(obj) diff --git a/examples/kustomization/sts-example/sample-clients/minio-sdk/python/requirements.txt b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/requirements.txt new file mode 100644 index 00000000000..32e16febc78 --- /dev/null +++ b/examples/kustomization/sts-example/sample-clients/minio-sdk/python/requirements.txt @@ -0,0 +1 @@ +minio>=7.1.13 \ No newline at end of file diff --git a/examples/kustomization/sts-example/sample-data/iam-setup-bucket.yaml b/examples/kustomization/sts-example/sample-data/iam-setup-bucket.yaml new file mode 100644 index 00000000000..9bcb39e4a1d --- /dev/null +++ b/examples/kustomization/sts-example/sample-data/iam-setup-bucket.yaml @@ -0,0 +1,63 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: start-config-script + namespace: minio-tenant-1 +data: + setup.sh: | + #!/bin/bash + mc mb local/test-bucket && \ + mc mb local/other-bucket && \ + mc admin policy add local test-bucket-rw /start-config/bucket-policy.json + bucket-policy.json: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:*" + ], + "Resource": [ + "arn:aws:s3:::test-bucket", + "arn:aws:s3:::test-bucket/*" + ] + } + ] + } +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: setup-bucket + namespace: minio-tenant-1 +spec: + backoffLimit: 5 + template: + spec: + restartPolicy: OnFailure + volumes: + - name: start-config + configMap: + name: start-config-script + defaultMode: 0744 + containers: + - name: mc + image: minio/mc + command: [ "/start-config/setup.sh" ] + volumeMounts: + - name: start-config + mountPath: /start-config/ + env: + - name: ACCESS_KEY + valueFrom: + secretKeyRef: + name: storage-user + key: CONSOLE_ACCESS_KEY + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: storage-user + key: CONSOLE_SECRET_KEY + - name: MC_HOST_local + value: https://$(ACCESS_KEY):$(SECRET_KEY)@minio.minio-tenant-1.svc.cluster.local diff --git a/examples/kustomization/sts-example/sample-data/kustomization.yaml b/examples/kustomization/sts-example/sample-data/kustomization.yaml new file mode 100644 index 00000000000..c0c07c4cdaf --- /dev/null +++ b/examples/kustomization/sts-example/sample-data/kustomization.yaml @@ -0,0 +1,4 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - iam-setup-bucket.yaml \ No newline at end of file diff --git a/examples/kustomization/sts-example/sts-app/kustomization.yaml b/examples/kustomization/sts-example/sts-app/kustomization.yaml new file mode 100644 index 00000000000..fb30da800bf --- /dev/null +++ b/examples/kustomization/sts-example/sts-app/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - policy-binding.yaml + - sts-client.yaml diff --git a/examples/kustomization/sts-example/sts-app/policy-binding.yaml b/examples/kustomization/sts-example/sts-app/policy-binding.yaml new file mode 100644 index 00000000000..13136d858e1 --- /dev/null +++ b/examples/kustomization/sts-example/sts-app/policy-binding.yaml @@ -0,0 +1,11 @@ +apiVersion: sts.min.io/v1alpha1 +kind: PolicyBinding +metadata: + name: binding-1 + namespace: minio-tenant-1 +spec: + application: + namespace: sts-client + serviceaccount: stsclient-sa + policies: + - test-bucket-rw diff --git a/examples/kustomization/sts-example/sts-app/sts-client.yaml b/examples/kustomization/sts-example/sts-app/sts-client.yaml new file mode 100644 index 00000000000..e812dcd519f --- /dev/null +++ b/examples/kustomization/sts-example/sts-app/sts-client.yaml @@ -0,0 +1,94 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: sts-client +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: sts-client + name: stsclient-sa +--- +apiVersion: v1 +kind: Secret +metadata: + name: sts-client-secret + namespace: sts-client + annotations: + kubernetes.io/service-account.name: stsclient-sa +type: kubernetes.io/service-account-token +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: sts-policy + namespace: sts-client +data: + policy.json: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetBucketLocation", + "s3:ListAllMyBuckets" + ], + "Resource": "arn:aws:s3:::*" + } + ] + } +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: sts-example-job + namespace: sts-client +spec: + backoffLimit: 10 + template: + spec: + restartPolicy: OnFailure + serviceAccountName: stsclient-sa + serviceAccount: stsclient-sa + containers: + - name: sts-client + image: miniodev/operator-sts-example:minio-go + imagePullPolicy: IfNotPresent + env: + - name: MINIO_ENDPOINT + value: https://minio.minio-tenant-1.svc.cluster.local:443 + - name: STS_ENDPOINT + value: https://sts.minio-operator.svc.cluster.local:4223/sts + - name: TENANT_NAMESPACE + value: minio-tenant-1 + - name: BUCKET + value: test-bucket + - name: AWS_WEB_IDENTITY_TOKEN_FILE + value: /var/run/secrets/kubernetes.io/serviceaccount/token + - name: STS_POLICY + value: /tmp/policy.json + - name: STS_CA_PATH # When Certmanager is used will load the ca in this file + value: /var/run/secrets/sts.min.io/ca.crt + - name: KUBERNETES_CA_PATH + value: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + volumeMounts: + - name: sts-policy + mountPath: /tmp/policy.json + subPath: policy.json + - name: tenant-certmanager-tls + mountPath: /var/run/secrets/sts.min.io/ + volumes: + - name: sts-policy + configMap: + name: sts-policy + defaultMode: 0744 + - name: tenant-certmanager-tls + projected: + sources: + - secret: + name: tenant-certmanager-tls + optional: true + items: + - key: ca.crt + path: ca.crt diff --git a/examples/kustomization/sts-example/tenant-certmanager/kustomization.yaml b/examples/kustomization/sts-example/tenant-certmanager/kustomization.yaml new file mode 100644 index 00000000000..2fe0d765445 --- /dev/null +++ b/examples/kustomization/sts-example/tenant-certmanager/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: minio-tenant-1 + +resources: + - ../../tenant-certmanager + +patchesJson6902: + - target: + group: minio.min.io + version: v2 + kind: Tenant + name: storage-certmanager + path: tenantNamePatch.yaml diff --git a/examples/kustomization/sts-example/tenant-certmanager/tenantNamePatch.yaml b/examples/kustomization/sts-example/tenant-certmanager/tenantNamePatch.yaml new file mode 100644 index 00000000000..6bd6f932e67 --- /dev/null +++ b/examples/kustomization/sts-example/tenant-certmanager/tenantNamePatch.yaml @@ -0,0 +1,3 @@ +- op: replace + path: /metadata/name + value: storage-policy-binding \ No newline at end of file diff --git a/examples/kustomization/sts-example/tenant/kustomization.yaml b/examples/kustomization/sts-example/tenant/kustomization.yaml new file mode 100644 index 00000000000..608cbe7fbd0 --- /dev/null +++ b/examples/kustomization/sts-example/tenant/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: minio-tenant-1 + +resources: + - ../../tenant-lite + +patchesJson6902: + - target: + group: minio.min.io + version: v2 + kind: Tenant + name: storage-lite + path: tenantNamePatch.yaml \ No newline at end of file diff --git a/examples/kustomization/sts-example/tenant/tenantNamePatch.yaml b/examples/kustomization/sts-example/tenant/tenantNamePatch.yaml new file mode 100644 index 00000000000..6bd6f932e67 --- /dev/null +++ b/examples/kustomization/sts-example/tenant/tenantNamePatch.yaml @@ -0,0 +1,3 @@ +- op: replace + path: /metadata/name + value: storage-policy-binding \ No newline at end of file diff --git a/helm/operator/templates/cluster-role.yaml b/helm/operator/templates/cluster-role.yaml index 2ff0468aed7..20cded809d1 100644 --- a/helm/operator/templates/cluster-role.yaml +++ b/helm/operator/templates/cluster-role.yaml @@ -130,6 +130,12 @@ rules: verbs: - approve - sign + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create - apiGroups: - minio.min.io resources: @@ -138,6 +144,7 @@ rules: - "*" - apiGroups: - min.io + - sts.min.io resources: - "*" verbs: diff --git a/helm/operator/templates/minio.min.io_tenants.yaml b/helm/operator/templates/minio.min.io_tenants.yaml index af4c11bcca1..be905ffacda 100644 --- a/helm/operator/templates/minio.min.io_tenants.yaml +++ b/helm/operator/templates/minio.min.io_tenants.yaml @@ -694,6 +694,18 @@ spec: type: integer resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -1751,6 +1763,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -1954,13 +1978,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -2138,6 +2175,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -2748,6 +2797,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -2954,13 +3015,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -3521,6 +3595,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -4176,6 +4262,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -4433,13 +4531,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -4778,13 +4889,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -5504,4 +5628,4 @@ spec: served: true storage: true subresources: - status: {} \ No newline at end of file + status: {} diff --git a/helm/operator/templates/operator-service.yaml b/helm/operator/templates/operator-service.yaml index 1a5273338c0..a4636e135f7 100644 --- a/helm/operator/templates/operator-service.yaml +++ b/helm/operator/templates/operator-service.yaml @@ -13,3 +13,18 @@ spec: selector: operator: leader {{- include "minio-operator.selectorLabels" . | nindent 4 }} +--- +apiVersion: v1 +kind: Service +metadata: + name: "sts" + namespace: {{ .Release.Namespace }} + labels: + {{- include "minio-operator.labels" . | nindent 4 }} +spec: + type: LoadBalancer + ports: + - port: 4223 + name: https + selector: + {{- include "minio-operator.selectorLabels" . | nindent 4 }} diff --git a/helm/operator/templates/sts.min.io_policybindings.yaml b/helm/operator/templates/sts.min.io_policybindings.yaml new file mode 100644 index 00000000000..b01576f5bda --- /dev/null +++ b/helm/operator/templates/sts.min.io_policybindings.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: policybindings.sts.min.io +spec: + group: sts.min.io + names: + kind: PolicyBinding + listKind: PolicyBindingList + plural: policybindings + shortNames: + - policybinding + singular: policybinding + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.currentState + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + application: + properties: + namespace: + type: string + serviceaccount: + type: string + required: + - namespace + - serviceaccount + type: object + policies: + items: + type: string + type: array + required: + - application + - policies + type: object + status: + properties: + currentState: + type: string + usage: + nullable: true + properties: + authotizations: + format: int64 + type: integer + type: object + required: + - currentState + - usage + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/helm/tenant/values.yaml b/helm/tenant/values.yaml index ceec9fffe48..88bcd07fc5d 100644 --- a/helm/tenant/values.yaml +++ b/helm/tenant/values.yaml @@ -95,7 +95,7 @@ tenant: ## not provided. DNS name format is *.minio.default.svc.cluster.local certConfig: {} ## MinIO features to enable or disable in the MinIO Tenant - ## https://github.com/minio/operator/blob/master/docs/crd.adoc#features + ## https://github.com/minio/operator/blob/master/docs/tenant_crd.adoc#features features: bucketDNS: false domains: {} diff --git a/k8s/update-codegen.sh b/k8s/update-codegen.sh index 6e8b15a65d0..3845bbb97b9 100755 --- a/k8s/update-codegen.sh +++ b/k8s/update-codegen.sh @@ -42,7 +42,7 @@ chmod +x ${CODEGEN_PKG}/generate-groups.sh cd ${SCRIPT_ROOT} ${CODEGEN_PKG}/generate-groups.sh "all" \ $ROOT_PKG/pkg/client $ROOT_PKG/pkg/apis \ - "minio.min.io:v2" \ + "minio.min.io:v2 sts.min.io:v1alpha1" \ --output-base "${TEMP_DIR}" \ --go-header-file "k8s/boilerplate.go.txt" diff --git a/kubectl-minio/go.sum b/kubectl-minio/go.sum index b0d6f7902e3..c749502d4dc 100644 --- a/kubectl-minio/go.sum +++ b/kubectl-minio/go.sum @@ -21,6 +21,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/kustomization.yaml b/kustomization.yaml index 7f06121d548..dbaaf8f8a94 100644 --- a/kustomization.yaml +++ b/kustomization.yaml @@ -11,7 +11,7 @@ resources: - resources/base/service-account.yaml - resources/base/cluster-role.yaml - resources/base/cluster-role-binding.yaml - - resources/base/crds/minio.min.io_tenants.yaml + - resources/base/crds/ - resources/base/service.yaml - resources/base/deployment.yaml - resources/base/console-ui.yaml diff --git a/pkg/apis/minio.min.io/v2/helper.go b/pkg/apis/minio.min.io/v2/helper.go index 04e61f0606e..05663f086ca 100644 --- a/pkg/apis/minio.min.io/v2/helper.go +++ b/pkg/apis/minio.min.io/v2/helper.go @@ -93,6 +93,12 @@ const ( WebhookCRDConversaion = WebhookAPIVersion + "/crd-conversion" ) +// STS API constants +const ( + STSDefaultPort = "4223" + STSEndpoint = "/sts" +) + type hostsTemplateValues struct { StatefulSet string CIService string diff --git a/pkg/apis/minio.min.io/v2/utils.go b/pkg/apis/minio.min.io/v2/utils.go index b90401cc1d5..f6690cb2037 100644 --- a/pkg/apis/minio.min.io/v2/utils.go +++ b/pkg/apis/minio.min.io/v2/utils.go @@ -15,7 +15,9 @@ package v2 import ( + "bytes" "crypto/rand" + "encoding/json" "fmt" "io" "strings" @@ -89,3 +91,13 @@ func GenerateTenantConfigurationFile(configuration map[string]string) string { } return rawConfiguration.String() } + +// CompactJSONString removes white spaces, tabs and line return +func CompactJSONString(jsonObject string) (string, error) { + objectByte := []byte(jsonObject) + buffer := new(bytes.Buffer) + if err := json.Compact(buffer, objectByte); err != nil { + return jsonObject, err + } + return buffer.String(), nil +} diff --git a/pkg/apis/sts.min.io/register.go b/pkg/apis/sts.min.io/register.go new file mode 100644 index 00000000000..7959139efe2 --- /dev/null +++ b/pkg/apis/sts.min.io/register.go @@ -0,0 +1,20 @@ +// Copyright (C) 2022, MinIO, Inc. +// +// This code is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License, version 3, +// along with this program. If not, see + +package operator + +// MinIO Operator STS group name. +const ( + GroupName = "sts.min.io" +) diff --git a/pkg/apis/sts.min.io/v1alpha1/doc.go b/pkg/apis/sts.min.io/v1alpha1/doc.go new file mode 100644 index 00000000000..3d55d26738c --- /dev/null +++ b/pkg/apis/sts.min.io/v1alpha1/doc.go @@ -0,0 +1,25 @@ +// Copyright (C) 2023, MinIO, Inc. +// +// This code is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License, version 3, +// along with this program. If not, see + +// +k8s:deepcopy-gen=package,register +// go:generate controller-gen crd:trivialVersions=true paths=. output:dir=. + +// Package v1alpha1 - The following parameters are specific to the `sts.min.io/v1alpha1` MinIO Policy Binding CRD API +// PolicyBinding is an Authorization mechanism managed by the Minio Operator. +// Using Kubernetes ServiceAccount JSON Web Tokens the binding allow a ServiceAccount to assume temporary IAM credentials. +// For more complete documentation on this object, see the https://docs.min.io/minio/k8s/reference/minio-operator-reference.html#minio-operator-yaml-reference[MinIO Kubernetes Documentation]. +// PolicyBinding is added as part of the MinIO Operator v5.0.0. + +// +groupName=sts.min.io +// +versionName=v1alpha1 +package v1alpha1 diff --git a/pkg/apis/sts.min.io/v1alpha1/register.go b/pkg/apis/sts.min.io/v1alpha1/register.go new file mode 100644 index 00000000000..f8daca904a9 --- /dev/null +++ b/pkg/apis/sts.min.io/v1alpha1/register.go @@ -0,0 +1,57 @@ +// Copyright (C) 2022, MinIO, Inc. +// +// This code is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License, version 3, +// along with this program. If not, see + +package v1alpha1 + +import ( + operator "github.com/minio/operator/pkg/apis/sts.min.io" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Version specifies the API Version +const Version = "v1alpha1" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: operator.GroupName, Version: Version} + +// Kind takes an unqualified kind and returns back a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // SchemeBuilder collects the scheme builder functions for the MinIO + // Operator API. + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + + // AddToScheme applies the SchemeBuilder functions to a specified scheme. + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &PolicyBinding{}, + &PolicyBindingList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/pkg/apis/sts.min.io/v1alpha1/types.go b/pkg/apis/sts.min.io/v1alpha1/types.go new file mode 100644 index 00000000000..660480822cc --- /dev/null +++ b/pkg/apis/sts.min.io/v1alpha1/types.go @@ -0,0 +1,86 @@ +// Copyright (C) 2022, MinIO, Inc. +// +// This code is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License, version 3, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License, version 3, +// along with this program. If not, see + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,shortName=policybinding,singular=policybinding +// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.currentState" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +kubebuilder:storageversion + +// PolicyBinding is a https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/[Kubernetes object] describing a MinIO PolicyBinding. +type PolicyBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // *Required* + + // + // The root field for the MinIO PolicyBinding object. + Spec PolicyBindingSpec `json:"spec,omitempty"` + + // Status provides details of the state of the PolicyBinding + // +optional + Status PolicyBindingStatus `json:"status,omitempty"` +} + +// PolicyBindingStatus is the status for a PolicyBinding resource +type PolicyBindingStatus struct { + // *Required* + + CurrentState string `json:"currentState"` + + // Keeps track of the invocations related to the PolicyBinding + // +nullable + Usage PolicyBindingUsage `json:"usage"` +} + +// PolicyBindingUsage are metrics regarding the usage of the policyBinding +type PolicyBindingUsage struct { + Authorizations int64 `json:"authotizations,omitempty"` +} + +// PolicyBindingSpec (`spec`) defines the configuration of a MinIO PolicyBinding object. + +type PolicyBindingSpec struct { + // *Required* + + // + // The Application Property identifies the namespace and service account that will be authorized + Application *Application `json:"application"` + // *Required* + + Policies []string `json:"policies"` +} + +// Application defines the `Namespace` and `ServiceAccount` to authorize the usage of the policies listed +type Application struct { + // *Required* + + Namespace string `json:"namespace"` + // *Required* + + ServiceAccount string `json:"serviceaccount"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PolicyBindingList is a list of PolicyBinding resources +type PolicyBindingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []PolicyBinding `json:"items"` +} diff --git a/pkg/apis/sts.min.io/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/sts.min.io/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..387b9d10a43 --- /dev/null +++ b/pkg/apis/sts.min.io/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,162 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Application) DeepCopyInto(out *Application) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application. +func (in *Application) DeepCopy() *Application { + if in == nil { + return nil + } + out := new(Application) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyBinding) DeepCopyInto(out *PolicyBinding) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyBinding. +func (in *PolicyBinding) DeepCopy() *PolicyBinding { + if in == nil { + return nil + } + out := new(PolicyBinding) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PolicyBinding) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyBindingList) DeepCopyInto(out *PolicyBindingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PolicyBinding, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyBindingList. +func (in *PolicyBindingList) DeepCopy() *PolicyBindingList { + if in == nil { + return nil + } + out := new(PolicyBindingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PolicyBindingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyBindingSpec) DeepCopyInto(out *PolicyBindingSpec) { + *out = *in + if in.Application != nil { + in, out := &in.Application, &out.Application + *out = new(Application) + **out = **in + } + if in.Policies != nil { + in, out := &in.Policies, &out.Policies + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyBindingSpec. +func (in *PolicyBindingSpec) DeepCopy() *PolicyBindingSpec { + if in == nil { + return nil + } + out := new(PolicyBindingSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyBindingStatus) DeepCopyInto(out *PolicyBindingStatus) { + *out = *in + out.Usage = in.Usage + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyBindingStatus. +func (in *PolicyBindingStatus) DeepCopy() *PolicyBindingStatus { + if in == nil { + return nil + } + out := new(PolicyBindingStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyBindingUsage) DeepCopyInto(out *PolicyBindingUsage) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyBindingUsage. +func (in *PolicyBindingUsage) DeepCopy() *PolicyBindingUsage { + if in == nil { + return nil + } + out := new(PolicyBindingUsage) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/client/applyconfiguration/sts.min.io/v1alpha1/application.go b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/application.go new file mode 100644 index 00000000000..83c93335ae7 --- /dev/null +++ b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/application.go @@ -0,0 +1,48 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// ApplicationApplyConfiguration represents an declarative configuration of the Application type for use +// with apply. +type ApplicationApplyConfiguration struct { + Namespace *string `json:"namespace,omitempty"` + ServiceAccount *string `json:"serviceaccount,omitempty"` +} + +// ApplicationApplyConfiguration constructs an declarative configuration of the Application type for use with +// apply. +func Application() *ApplicationApplyConfiguration { + return &ApplicationApplyConfiguration{} +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ApplicationApplyConfiguration) WithNamespace(value string) *ApplicationApplyConfiguration { + b.Namespace = &value + return b +} + +// WithServiceAccount sets the ServiceAccount field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceAccount field is set to the value of the last call. +func (b *ApplicationApplyConfiguration) WithServiceAccount(value string) *ApplicationApplyConfiguration { + b.ServiceAccount = &value + return b +} diff --git a/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybinding.go b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybinding.go new file mode 100644 index 00000000000..f8e98d4dada --- /dev/null +++ b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybinding.go @@ -0,0 +1,219 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// PolicyBindingApplyConfiguration represents an declarative configuration of the PolicyBinding type for use +// with apply. +type PolicyBindingApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *PolicyBindingSpecApplyConfiguration `json:"spec,omitempty"` + Status *PolicyBindingStatusApplyConfiguration `json:"status,omitempty"` +} + +// PolicyBinding constructs an declarative configuration of the PolicyBinding type for use with +// apply. +func PolicyBinding(name, namespace string) *PolicyBindingApplyConfiguration { + b := &PolicyBindingApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("PolicyBinding") + b.WithAPIVersion("sts.min.io/v1alpha1") + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithKind(value string) *PolicyBindingApplyConfiguration { + b.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithAPIVersion(value string) *PolicyBindingApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithName(value string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithGenerateName(value string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithNamespace(value string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithUID(value types.UID) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithResourceVersion(value string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithGeneration(value int64) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithCreationTimestamp(value metav1.Time) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *PolicyBindingApplyConfiguration) WithLabels(entries map[string]string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *PolicyBindingApplyConfiguration) WithAnnotations(entries map[string]string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *PolicyBindingApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.OwnerReferences = append(b.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *PolicyBindingApplyConfiguration) WithFinalizers(values ...string) *PolicyBindingApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.Finalizers = append(b.Finalizers, values[i]) + } + return b +} + +func (b *PolicyBindingApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithSpec(value *PolicyBindingSpecApplyConfiguration) *PolicyBindingApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *PolicyBindingApplyConfiguration) WithStatus(value *PolicyBindingStatusApplyConfiguration) *PolicyBindingApplyConfiguration { + b.Status = value + return b +} diff --git a/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingspec.go b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingspec.go new file mode 100644 index 00000000000..7eee72667f7 --- /dev/null +++ b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingspec.go @@ -0,0 +1,50 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// PolicyBindingSpecApplyConfiguration represents an declarative configuration of the PolicyBindingSpec type for use +// with apply. +type PolicyBindingSpecApplyConfiguration struct { + Application *ApplicationApplyConfiguration `json:"application,omitempty"` + Policies []string `json:"policies,omitempty"` +} + +// PolicyBindingSpecApplyConfiguration constructs an declarative configuration of the PolicyBindingSpec type for use with +// apply. +func PolicyBindingSpec() *PolicyBindingSpecApplyConfiguration { + return &PolicyBindingSpecApplyConfiguration{} +} + +// WithApplication sets the Application field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Application field is set to the value of the last call. +func (b *PolicyBindingSpecApplyConfiguration) WithApplication(value *ApplicationApplyConfiguration) *PolicyBindingSpecApplyConfiguration { + b.Application = value + return b +} + +// WithPolicies adds the given value to the Policies field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Policies field. +func (b *PolicyBindingSpecApplyConfiguration) WithPolicies(values ...string) *PolicyBindingSpecApplyConfiguration { + for i := range values { + b.Policies = append(b.Policies, values[i]) + } + return b +} diff --git a/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingstatus.go b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingstatus.go new file mode 100644 index 00000000000..cf51856c5b1 --- /dev/null +++ b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingstatus.go @@ -0,0 +1,48 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// PolicyBindingStatusApplyConfiguration represents an declarative configuration of the PolicyBindingStatus type for use +// with apply. +type PolicyBindingStatusApplyConfiguration struct { + CurrentState *string `json:"currentState,omitempty"` + Usage *PolicyBindingUsageApplyConfiguration `json:"usage,omitempty"` +} + +// PolicyBindingStatusApplyConfiguration constructs an declarative configuration of the PolicyBindingStatus type for use with +// apply. +func PolicyBindingStatus() *PolicyBindingStatusApplyConfiguration { + return &PolicyBindingStatusApplyConfiguration{} +} + +// WithCurrentState sets the CurrentState field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CurrentState field is set to the value of the last call. +func (b *PolicyBindingStatusApplyConfiguration) WithCurrentState(value string) *PolicyBindingStatusApplyConfiguration { + b.CurrentState = &value + return b +} + +// WithUsage sets the Usage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Usage field is set to the value of the last call. +func (b *PolicyBindingStatusApplyConfiguration) WithUsage(value *PolicyBindingUsageApplyConfiguration) *PolicyBindingStatusApplyConfiguration { + b.Usage = value + return b +} diff --git a/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingusage.go b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingusage.go new file mode 100644 index 00000000000..1407845b651 --- /dev/null +++ b/pkg/client/applyconfiguration/sts.min.io/v1alpha1/policybindingusage.go @@ -0,0 +1,39 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +// PolicyBindingUsageApplyConfiguration represents an declarative configuration of the PolicyBindingUsage type for use +// with apply. +type PolicyBindingUsageApplyConfiguration struct { + Authorizations *int64 `json:"authotizations,omitempty"` +} + +// PolicyBindingUsageApplyConfiguration constructs an declarative configuration of the PolicyBindingUsage type for use with +// apply. +func PolicyBindingUsage() *PolicyBindingUsageApplyConfiguration { + return &PolicyBindingUsageApplyConfiguration{} +} + +// WithAuthorizations sets the Authorizations field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Authorizations field is set to the value of the last call. +func (b *PolicyBindingUsageApplyConfiguration) WithAuthorizations(value int64) *PolicyBindingUsageApplyConfiguration { + b.Authorizations = &value + return b +} diff --git a/pkg/client/applyconfiguration/utils.go b/pkg/client/applyconfiguration/utils.go index 753bb05fd8d..28a6cf0d202 100644 --- a/pkg/client/applyconfiguration/utils.go +++ b/pkg/client/applyconfiguration/utils.go @@ -20,7 +20,9 @@ package applyconfiguration import ( v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" miniominiov2 "github.com/minio/operator/pkg/client/applyconfiguration/minio.min.io/v2" + stsminiov1alpha1 "github.com/minio/operator/pkg/client/applyconfiguration/sts.min.io/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -82,6 +84,18 @@ func ForKind(kind schema.GroupVersionKind) interface{} { case v2.SchemeGroupVersion.WithKind("TierUsage"): return &miniominiov2.TierUsageApplyConfiguration{} + // Group=sts.min.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithKind("Application"): + return &stsminiov1alpha1.ApplicationApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PolicyBinding"): + return &stsminiov1alpha1.PolicyBindingApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PolicyBindingSpec"): + return &stsminiov1alpha1.PolicyBindingSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PolicyBindingStatus"): + return &stsminiov1alpha1.PolicyBindingStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("PolicyBindingUsage"): + return &stsminiov1alpha1.PolicyBindingUsageApplyConfiguration{} + } return nil } diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index da3287f43d2..babee4e91b4 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -23,6 +23,7 @@ import ( "net/http" miniov2 "github.com/minio/operator/pkg/client/clientset/versioned/typed/minio.min.io/v2" + stsv1alpha1 "github.com/minio/operator/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1" discovery "k8s.io/client-go/discovery" rest "k8s.io/client-go/rest" flowcontrol "k8s.io/client-go/util/flowcontrol" @@ -31,12 +32,14 @@ import ( type Interface interface { Discovery() discovery.DiscoveryInterface MinioV2() miniov2.MinioV2Interface + StsV1alpha1() stsv1alpha1.StsV1alpha1Interface } // Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient - minioV2 *miniov2.MinioV2Client + minioV2 *miniov2.MinioV2Client + stsV1alpha1 *stsv1alpha1.StsV1alpha1Client } // MinioV2 retrieves the MinioV2Client @@ -44,6 +47,11 @@ func (c *Clientset) MinioV2() miniov2.MinioV2Interface { return c.minioV2 } +// StsV1alpha1 retrieves the StsV1alpha1Client +func (c *Clientset) StsV1alpha1() stsv1alpha1.StsV1alpha1Interface { + return c.stsV1alpha1 +} + // Discovery retrieves the DiscoveryClient func (c *Clientset) Discovery() discovery.DiscoveryInterface { if c == nil { @@ -92,6 +100,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, if err != nil { return nil, err } + cs.stsV1alpha1, err = stsv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) if err != nil { @@ -114,6 +126,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset { func New(c rest.Interface) *Clientset { var cs Clientset cs.minioV2 = miniov2.New(c) + cs.stsV1alpha1 = stsv1alpha1.New(c) cs.DiscoveryClient = discovery.NewDiscoveryClient(c) return &cs diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index 016a298f4bc..c8d5beb4a90 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -22,6 +22,8 @@ import ( clientset "github.com/minio/operator/pkg/client/clientset/versioned" miniov2 "github.com/minio/operator/pkg/client/clientset/versioned/typed/minio.min.io/v2" fakeminiov2 "github.com/minio/operator/pkg/client/clientset/versioned/typed/minio.min.io/v2/fake" + stsv1alpha1 "github.com/minio/operator/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1" + fakestsv1alpha1 "github.com/minio/operator/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -83,3 +85,8 @@ var ( func (c *Clientset) MinioV2() miniov2.MinioV2Interface { return &fakeminiov2.FakeMinioV2{Fake: &c.Fake} } + +// StsV1alpha1 retrieves the StsV1alpha1Client +func (c *Clientset) StsV1alpha1() stsv1alpha1.StsV1alpha1Interface { + return &fakestsv1alpha1.FakeStsV1alpha1{Fake: &c.Fake} +} diff --git a/pkg/client/clientset/versioned/fake/register.go b/pkg/client/clientset/versioned/fake/register.go index 74e9fece0f2..5e316060f23 100644 --- a/pkg/client/clientset/versioned/fake/register.go +++ b/pkg/client/clientset/versioned/fake/register.go @@ -20,6 +20,7 @@ package fake import ( miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + stsv1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,6 +33,7 @@ var codecs = serializer.NewCodecFactory(scheme) var localSchemeBuilder = runtime.SchemeBuilder{ miniov2.AddToScheme, + stsv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/client/clientset/versioned/scheme/register.go b/pkg/client/clientset/versioned/scheme/register.go index 98e7e9c1958..bb942db90e0 100644 --- a/pkg/client/clientset/versioned/scheme/register.go +++ b/pkg/client/clientset/versioned/scheme/register.go @@ -20,6 +20,7 @@ package scheme import ( miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + stsv1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -32,6 +33,7 @@ var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ miniov2.AddToScheme, + stsv1alpha1.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go new file mode 100644 index 00000000000..8d796276602 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/doc.go @@ -0,0 +1,20 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go new file mode 100644 index 00000000000..af8d23ae280 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go new file mode 100644 index 00000000000..8df61d2283f --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_policybinding.go @@ -0,0 +1,189 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + json "encoding/json" + "fmt" + + v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" + stsminiov1alpha1 "github.com/minio/operator/pkg/client/applyconfiguration/sts.min.io/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakePolicyBindings implements PolicyBindingInterface +type FakePolicyBindings struct { + Fake *FakeStsV1alpha1 + ns string +} + +var policybindingsResource = v1alpha1.SchemeGroupVersion.WithResource("policybindings") + +var policybindingsKind = v1alpha1.SchemeGroupVersion.WithKind("PolicyBinding") + +// Get takes name of the policyBinding, and returns the corresponding policyBinding object, and an error if there is any. +func (c *FakePolicyBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.PolicyBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(policybindingsResource, c.ns, name), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} + +// List takes label and field selectors, and returns the list of PolicyBindings that match those selectors. +func (c *FakePolicyBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.PolicyBindingList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(policybindingsResource, policybindingsKind, c.ns, opts), &v1alpha1.PolicyBindingList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha1.PolicyBindingList{ListMeta: obj.(*v1alpha1.PolicyBindingList).ListMeta} + for _, item := range obj.(*v1alpha1.PolicyBindingList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested policyBindings. +func (c *FakePolicyBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(policybindingsResource, c.ns, opts)) + +} + +// Create takes the representation of a policyBinding and creates it. Returns the server's representation of the policyBinding, and an error, if there is any. +func (c *FakePolicyBindings) Create(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.CreateOptions) (result *v1alpha1.PolicyBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(policybindingsResource, c.ns, policyBinding), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} + +// Update takes the representation of a policyBinding and updates it. Returns the server's representation of the policyBinding, and an error, if there is any. +func (c *FakePolicyBindings) Update(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.UpdateOptions) (result *v1alpha1.PolicyBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(policybindingsResource, c.ns, policyBinding), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakePolicyBindings) UpdateStatus(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.UpdateOptions) (*v1alpha1.PolicyBinding, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(policybindingsResource, "status", c.ns, policyBinding), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} + +// Delete takes name of the policyBinding and deletes it. Returns an error if one occurs. +func (c *FakePolicyBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(policybindingsResource, c.ns, name, opts), &v1alpha1.PolicyBinding{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakePolicyBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(policybindingsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha1.PolicyBindingList{}) + return err +} + +// Patch applies the patch and returns the patched policyBinding. +func (c *FakePolicyBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.PolicyBinding, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(policybindingsResource, c.ns, name, pt, data, subresources...), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} + +// Apply takes the given apply declarative configuration, applies it and returns the applied policyBinding. +func (c *FakePolicyBindings) Apply(ctx context.Context, policyBinding *stsminiov1alpha1.PolicyBindingApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.PolicyBinding, err error) { + if policyBinding == nil { + return nil, fmt.Errorf("policyBinding provided to Apply must not be nil") + } + data, err := json.Marshal(policyBinding) + if err != nil { + return nil, err + } + name := policyBinding.Name + if name == nil { + return nil, fmt.Errorf("policyBinding.Name must be provided to Apply") + } + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(policybindingsResource, c.ns, *name, types.ApplyPatchType, data), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} + +// ApplyStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). +func (c *FakePolicyBindings) ApplyStatus(ctx context.Context, policyBinding *stsminiov1alpha1.PolicyBindingApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.PolicyBinding, err error) { + if policyBinding == nil { + return nil, fmt.Errorf("policyBinding provided to Apply must not be nil") + } + data, err := json.Marshal(policyBinding) + if err != nil { + return nil, err + } + name := policyBinding.Name + if name == nil { + return nil, fmt.Errorf("policyBinding.Name must be provided to Apply") + } + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(policybindingsResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1alpha1.PolicyBinding{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.PolicyBinding), err +} diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go new file mode 100644 index 00000000000..ecbfb218923 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/fake/fake_sts.min.io_client.go @@ -0,0 +1,40 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/minio/operator/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeStsV1alpha1 struct { + *testing.Fake +} + +func (c *FakeStsV1alpha1) PolicyBindings(namespace string) v1alpha1.PolicyBindingInterface { + return &FakePolicyBindings{c, namespace} +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeStsV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go new file mode 100644 index 00000000000..8afaf86213e --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +type PolicyBindingExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go new file mode 100644 index 00000000000..2cd673123b5 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/policybinding.go @@ -0,0 +1,256 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + json "encoding/json" + "fmt" + "time" + + v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" + stsminiov1alpha1 "github.com/minio/operator/pkg/client/applyconfiguration/sts.min.io/v1alpha1" + scheme "github.com/minio/operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// PolicyBindingsGetter has a method to return a PolicyBindingInterface. +// A group's client should implement this interface. +type PolicyBindingsGetter interface { + PolicyBindings(namespace string) PolicyBindingInterface +} + +// PolicyBindingInterface has methods to work with PolicyBinding resources. +type PolicyBindingInterface interface { + Create(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.CreateOptions) (*v1alpha1.PolicyBinding, error) + Update(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.UpdateOptions) (*v1alpha1.PolicyBinding, error) + UpdateStatus(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.UpdateOptions) (*v1alpha1.PolicyBinding, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.PolicyBinding, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.PolicyBindingList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.PolicyBinding, err error) + Apply(ctx context.Context, policyBinding *stsminiov1alpha1.PolicyBindingApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.PolicyBinding, err error) + ApplyStatus(ctx context.Context, policyBinding *stsminiov1alpha1.PolicyBindingApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.PolicyBinding, err error) + PolicyBindingExpansion +} + +// policyBindings implements PolicyBindingInterface +type policyBindings struct { + client rest.Interface + ns string +} + +// newPolicyBindings returns a PolicyBindings +func newPolicyBindings(c *StsV1alpha1Client, namespace string) *policyBindings { + return &policyBindings{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the policyBinding, and returns the corresponding policyBinding object, and an error if there is any. +func (c *policyBindings) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.PolicyBinding, err error) { + result = &v1alpha1.PolicyBinding{} + err = c.client.Get(). + Namespace(c.ns). + Resource("policybindings"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of PolicyBindings that match those selectors. +func (c *policyBindings) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.PolicyBindingList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha1.PolicyBindingList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("policybindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested policyBindings. +func (c *policyBindings) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("policybindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a policyBinding and creates it. Returns the server's representation of the policyBinding, and an error, if there is any. +func (c *policyBindings) Create(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.CreateOptions) (result *v1alpha1.PolicyBinding, err error) { + result = &v1alpha1.PolicyBinding{} + err = c.client.Post(). + Namespace(c.ns). + Resource("policybindings"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(policyBinding). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a policyBinding and updates it. Returns the server's representation of the policyBinding, and an error, if there is any. +func (c *policyBindings) Update(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.UpdateOptions) (result *v1alpha1.PolicyBinding, err error) { + result = &v1alpha1.PolicyBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("policybindings"). + Name(policyBinding.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(policyBinding). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *policyBindings) UpdateStatus(ctx context.Context, policyBinding *v1alpha1.PolicyBinding, opts v1.UpdateOptions) (result *v1alpha1.PolicyBinding, err error) { + result = &v1alpha1.PolicyBinding{} + err = c.client.Put(). + Namespace(c.ns). + Resource("policybindings"). + Name(policyBinding.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(policyBinding). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the policyBinding and deletes it. Returns an error if one occurs. +func (c *policyBindings) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("policybindings"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *policyBindings) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("policybindings"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched policyBinding. +func (c *policyBindings) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.PolicyBinding, err error) { + result = &v1alpha1.PolicyBinding{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("policybindings"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} + +// Apply takes the given apply declarative configuration, applies it and returns the applied policyBinding. +func (c *policyBindings) Apply(ctx context.Context, policyBinding *stsminiov1alpha1.PolicyBindingApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.PolicyBinding, err error) { + if policyBinding == nil { + return nil, fmt.Errorf("policyBinding provided to Apply must not be nil") + } + patchOpts := opts.ToPatchOptions() + data, err := json.Marshal(policyBinding) + if err != nil { + return nil, err + } + name := policyBinding.Name + if name == nil { + return nil, fmt.Errorf("policyBinding.Name must be provided to Apply") + } + result = &v1alpha1.PolicyBinding{} + err = c.client.Patch(types.ApplyPatchType). + Namespace(c.ns). + Resource("policybindings"). + Name(*name). + VersionedParams(&patchOpts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} + +// ApplyStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). +func (c *policyBindings) ApplyStatus(ctx context.Context, policyBinding *stsminiov1alpha1.PolicyBindingApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.PolicyBinding, err error) { + if policyBinding == nil { + return nil, fmt.Errorf("policyBinding provided to Apply must not be nil") + } + patchOpts := opts.ToPatchOptions() + data, err := json.Marshal(policyBinding) + if err != nil { + return nil, err + } + + name := policyBinding.Name + if name == nil { + return nil, fmt.Errorf("policyBinding.Name must be provided to Apply") + } + + result = &v1alpha1.PolicyBinding{} + err = c.client.Patch(types.ApplyPatchType). + Namespace(c.ns). + Resource("policybindings"). + Name(*name). + SubResource("status"). + VersionedParams(&patchOpts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go new file mode 100644 index 00000000000..1b3adb7905b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/sts.min.io/v1alpha1/sts.min.io_client.go @@ -0,0 +1,107 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "net/http" + + v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" + "github.com/minio/operator/pkg/client/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type StsV1alpha1Interface interface { + RESTClient() rest.Interface + PolicyBindingsGetter +} + +// StsV1alpha1Client is used to interact with features provided by the sts.min.io group. +type StsV1alpha1Client struct { + restClient rest.Interface +} + +func (c *StsV1alpha1Client) PolicyBindings(namespace string) PolicyBindingInterface { + return newPolicyBindings(c, namespace) +} + +// NewForConfig creates a new StsV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*StsV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new StsV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*StsV1alpha1Client, error) { + config := *c + if err := setConfigDefaults(&config); err != nil { + return nil, err + } + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &StsV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new StsV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *StsV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new StsV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *StsV1alpha1Client { + return &StsV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) error { + gv := v1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + return nil +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *StsV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index 18103c93857..2263a2b8c6b 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -26,6 +26,7 @@ import ( versioned "github.com/minio/operator/pkg/client/clientset/versioned" internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces" miniominio "github.com/minio/operator/pkg/client/informers/externalversions/minio.min.io" + stsminio "github.com/minio/operator/pkg/client/informers/externalversions/sts.min.io" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" @@ -244,8 +245,13 @@ type SharedInformerFactory interface { InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer Minio() miniominio.Interface + Sts() stsminio.Interface } func (f *sharedInformerFactory) Minio() miniominio.Interface { return miniominio.New(f, f.namespace, f.tweakListOptions) } + +func (f *sharedInformerFactory) Sts() stsminio.Interface { + return stsminio.New(f, f.namespace, f.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 7829974646c..1c93ddc9ea6 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -22,6 +22,7 @@ import ( "fmt" v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" schema "k8s.io/apimachinery/pkg/runtime/schema" cache "k8s.io/client-go/tools/cache" ) @@ -56,6 +57,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource case v2.SchemeGroupVersion.WithResource("tenants"): return &genericInformer{resource: resource.GroupResource(), informer: f.Minio().V2().Tenants().Informer()}, nil + // Group=sts.min.io, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("policybindings"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Sts().V1alpha1().PolicyBindings().Informer()}, nil + } return nil, fmt.Errorf("no informer found for %v", resource) diff --git a/pkg/client/informers/externalversions/sts.min.io/interface.go b/pkg/client/informers/externalversions/sts.min.io/interface.go new file mode 100644 index 00000000000..ffa5a13b17f --- /dev/null +++ b/pkg/client/informers/externalversions/sts.min.io/interface.go @@ -0,0 +1,46 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by informer-gen. DO NOT EDIT. + +package sts + +import ( + internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/minio/operator/pkg/client/informers/externalversions/sts.min.io/v1alpha1" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1alpha1 provides access to shared informers for resources in V1alpha1. + V1alpha1() v1alpha1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1alpha1 returns a new v1alpha1.Interface. +func (g *group) V1alpha1() v1alpha1.Interface { + return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go new file mode 100644 index 00000000000..9d5eba602fe --- /dev/null +++ b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/interface.go @@ -0,0 +1,45 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // PolicyBindings returns a PolicyBindingInformer. + PolicyBindings() PolicyBindingInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// PolicyBindings returns a PolicyBindingInformer. +func (v *version) PolicyBindings() PolicyBindingInformer { + return &policyBindingInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} diff --git a/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go new file mode 100644 index 00000000000..feaa08ebe3f --- /dev/null +++ b/pkg/client/informers/externalversions/sts.min.io/v1alpha1/policybinding.go @@ -0,0 +1,90 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "context" + time "time" + + stsminiov1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" + versioned "github.com/minio/operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/minio/operator/pkg/client/informers/externalversions/internalinterfaces" + v1alpha1 "github.com/minio/operator/pkg/client/listers/sts.min.io/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// PolicyBindingInformer provides access to a shared informer and lister for +// PolicyBindings. +type PolicyBindingInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha1.PolicyBindingLister +} + +type policyBindingInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewPolicyBindingInformer constructs a new informer for PolicyBinding type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewPolicyBindingInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredPolicyBindingInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredPolicyBindingInformer constructs a new informer for PolicyBinding type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredPolicyBindingInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.StsV1alpha1().PolicyBindings(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.StsV1alpha1().PolicyBindings(namespace).Watch(context.TODO(), options) + }, + }, + &stsminiov1alpha1.PolicyBinding{}, + resyncPeriod, + indexers, + ) +} + +func (f *policyBindingInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredPolicyBindingInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *policyBindingInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&stsminiov1alpha1.PolicyBinding{}, f.defaultInformer) +} + +func (f *policyBindingInformer) Lister() v1alpha1.PolicyBindingLister { + return v1alpha1.NewPolicyBindingLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go b/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go new file mode 100644 index 00000000000..d6a4a571087 --- /dev/null +++ b/pkg/client/listers/sts.min.io/v1alpha1/expansion_generated.go @@ -0,0 +1,27 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +// PolicyBindingListerExpansion allows custom methods to be added to +// PolicyBindingLister. +type PolicyBindingListerExpansion interface{} + +// PolicyBindingNamespaceListerExpansion allows custom methods to be added to +// PolicyBindingNamespaceLister. +type PolicyBindingNamespaceListerExpansion interface{} diff --git a/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go b/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go new file mode 100644 index 00000000000..4f1384257d5 --- /dev/null +++ b/pkg/client/listers/sts.min.io/v1alpha1/policybinding.go @@ -0,0 +1,99 @@ +// This file is part of MinIO Operator +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1alpha1 "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// PolicyBindingLister helps list PolicyBindings. +// All objects returned here must be treated as read-only. +type PolicyBindingLister interface { + // List lists all PolicyBindings in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.PolicyBinding, err error) + // PolicyBindings returns an object that can list and get PolicyBindings. + PolicyBindings(namespace string) PolicyBindingNamespaceLister + PolicyBindingListerExpansion +} + +// policyBindingLister implements the PolicyBindingLister interface. +type policyBindingLister struct { + indexer cache.Indexer +} + +// NewPolicyBindingLister returns a new PolicyBindingLister. +func NewPolicyBindingLister(indexer cache.Indexer) PolicyBindingLister { + return &policyBindingLister{indexer: indexer} +} + +// List lists all PolicyBindings in the indexer. +func (s *policyBindingLister) List(selector labels.Selector) (ret []*v1alpha1.PolicyBinding, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.PolicyBinding)) + }) + return ret, err +} + +// PolicyBindings returns an object that can list and get PolicyBindings. +func (s *policyBindingLister) PolicyBindings(namespace string) PolicyBindingNamespaceLister { + return policyBindingNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// PolicyBindingNamespaceLister helps list and get PolicyBindings. +// All objects returned here must be treated as read-only. +type PolicyBindingNamespaceLister interface { + // List lists all PolicyBindings in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha1.PolicyBinding, err error) + // Get retrieves the PolicyBinding from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha1.PolicyBinding, error) + PolicyBindingNamespaceListerExpansion +} + +// policyBindingNamespaceLister implements the PolicyBindingNamespaceLister +// interface. +type policyBindingNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all PolicyBindings in the indexer for a given namespace. +func (s policyBindingNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.PolicyBinding, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha1.PolicyBinding)) + }) + return ret, err +} + +// Get retrieves the PolicyBinding from the indexer for a given namespace and name. +func (s policyBindingNamespaceLister) Get(name string) (*v1alpha1.PolicyBinding, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha1.Resource("policybinding"), name) + } + return obj.(*v1alpha1.PolicyBinding), nil +} diff --git a/pkg/controller/cluster/http_handlers.go b/pkg/controller/cluster/http_handlers.go index a6f357a6b5a..6762c2c8068 100644 --- a/pkg/controller/cluster/http_handlers.go +++ b/pkg/controller/cluster/http_handlers.go @@ -16,14 +16,24 @@ package cluster +//lint:file-ignore ST1005 Incorrectly formatted error string + import ( + "bytes" + "context" + "encoding/json" + "errors" "fmt" "net/http" "strconv" "strings" + "github.com/minio/operator/pkg/apis/sts.min.io/v1alpha1" + iampolicy "github.com/minio/pkg/iam/policy" + "github.com/gorilla/mux" miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + xhttp "github.com/minio/operator/pkg/internal" "github.com/minio/operator/pkg/resources/services" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,6 +48,8 @@ const ( updatePath = "/tmp" + miniov2.WebhookAPIUpdate + slashSeparator ) +const contextLogKey = contextKeyType("operatorlog") + // BucketSrvHandler - POST /webhook/v1/bucketsrv/{namespace}/{name}?bucket={bucket} func (c *Controller) BucketSrvHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -116,3 +128,226 @@ func validateBucketName(bucket string) (bool, error) { } return true, nil } + +// AssumeRoleWithWebIdentityHandler - POST /sts/{tenantNamespace} +// AssumeRoleWithWebIdentity - implementation of AWS STS API. +// Authenticates a Kubernetes Service accounts using a JWT Token +// Evalues a PolicyBinding CRD as Mapping of the Minio Policies that the ServiceAccount can assume on a minio tenant +// Eg:- +// $ curl -k -X POST https://operator:9443/sts/{tenantNamespace} -d "Version=2011-06-15&Action=AssumeRoleWithWebIdentity&WebIdentityToken=" -H "Content-Type: application/x-www-form-urlencoded" +func (c *Controller) AssumeRoleWithWebIdentityHandler(w http.ResponseWriter, r *http.Request) { + routerVars := mux.Vars(r) + tenantNamespace := "" + tenantNamespace, err := xhttp.UnescapeQueryPath(routerVars["tenantNamespace"]) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unable to unescape tenant namespace: %s", err)) + return + } + + reqInfo := ReqInfo{ + RequestID: w.Header().Get(AmzRequestID), + RemoteHost: xhttp.GetSourceIPFromHeaders(r), + Host: r.Host, + UserAgent: r.UserAgent(), + API: webIdentity, + TenantNamespace: tenantNamespace, + } + + ctx := context.WithValue(r.Context(), contextLogKey, &reqInfo) + + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Tenant namespace is missing:, %s", err)) + return + } + + // Parse the incoming form data. + if err := xhttp.ParseForm(r); err != nil { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Error parsing request: %s", err)) + return + } + + if r.Form.Get(stsVersion) != stsAPIVersion { + err := fmt.Errorf("invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion) + writeSTSErrorResponse(w, true, ErrSTSMissingParameter, err) + return + } + + action := r.Form.Get(stsAction) + switch action { + // For now we only do WebIdentity, leaving it in case we want to implement certificate authentication + case webIdentity: + default: + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Unsupported action %s", action)) + return + } + + token := strings.TrimSpace(r.Form.Get(stsWebIdentityToken)) + + if token == "" { + writeSTSErrorResponse(w, true, ErrSTSMissingParameter, fmt.Errorf("Missing parameter '%s'", stsWebIdentityToken)) + return + } + + // VALIDATE JWT + accessToken := r.Form.Get(stsWebIdentityToken) + saAuthResult, err := c.ValidateServiceAccountJWT(&ctx, accessToken) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInvalidIdentityToken, err) + return + } + + if !saAuthResult.Status.Authenticated { + writeSTSErrorResponse(w, true, ErrSTSAccessDenied, fmt.Errorf("Access denied: Invalid Token")) + return + } + chunks := strings.Split(strings.Replace(saAuthResult.Status.User.Username, "system:serviceaccount:", "", -1), ":") + + if len(chunks) < 2 { + writeSTSErrorResponse(w, true, ErrSTSInvalidIdentityToken, fmt.Errorf("Error parsing service account name and namespace")) + return + } + // saNamespace Service account Namespace + saNamespace := chunks[0] + // saName service account username + saName := chunks[1] + + // Authorized PolicyBindings for the Service Account + policyBindings := []v1alpha1.PolicyBinding{} + pbs, err := c.minioClientSet.StsV1alpha1().PolicyBindings(tenantNamespace).List(ctx, metav1.ListOptions{}) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInternalError, fmt.Errorf("Error obtaining PolicyBindings: %s", err)) + return + } + + for _, pb := range pbs.Items { + if pb.Spec.Application.Namespace == saNamespace && pb.Spec.Application.ServiceAccount == saName { + policyBindings = append(policyBindings, pb) + } + } + if len(policyBindings) == 0 { + writeSTSErrorResponse(w, true, ErrSTSAccessDenied, fmt.Errorf("Service account '%s' has no PolicyBindings in namespace '%s'", saAuthResult.Status.User.Username, tenantNamespace)) + return + } + + tenants, err := c.minioClientSet.MinioV2().Tenants(tenantNamespace).List(ctx, metav1.ListOptions{}) + + if err != nil || len(tenants.Items) == 0 { + if k8serrors.IsNotFound(err) { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("No tenant found in namespace '%s'", tenantNamespace)) + return + } + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Error getting tenant in namespace '%s'", tenantNamespace)) + return + } + + // Only one tenant is allowed in a single namespace, gathering the first tenant in the list + tenant := tenants.Items[0] + + tenantConfiguration, err := c.getTenantCredentials(ctx, &tenant) + if err != nil { + if errors.Is(err, ErrEmptyRootCredentials) { + writeSTSErrorResponse(w, true, ErrSTSInternalError, fmt.Errorf("Tenant '%s' is missing root credentials", tenant.Name)) + return + } + writeSTSErrorResponse(w, true, ErrSTSInternalError, fmt.Errorf("Error getting tenant '%s' root credentials: %s", tenant.Name, err)) + return + } + adminClient, err := tenant.NewMinIOAdmin(tenantConfiguration, c.getTransport()) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInternalError, fmt.Errorf("Error communicating with tenant '%s': %s", tenant.Name, err)) + return + } + + // Session Policy + sessionPolicyStr := r.Form.Get(stsPolicy) + var compactedSessionPolicy string + var sessionPolicy *iampolicy.Policy + if len(sessionPolicyStr) > 0 { + compactedSessionPolicy, err = miniov2.CompactJSONString(sessionPolicyStr) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSMalformedPolicyDocument, err) + return + } + sessionPolicy, err = iampolicy.ParseConfig(bytes.NewReader([]byte(compactedSessionPolicy))) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSMalformedPolicyDocument, err) + return + } + // Version in policy must not be empty + if sessionPolicy.Version == "" { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid session policy version")) + return + } + // The plain text that you use for both inline and managed session + // policies shouldn't exceed 2048 characters. + if len(compactedSessionPolicy) > 2048 { + writeSTSErrorResponse(w, true, ErrSTSPackedPolicyTooLarge, fmt.Errorf("Session policy should not exceed 2048 characters")) + return + } + } + + var bfPolicy iampolicy.Policy + for _, pb := range policyBindings { + if sessionPolicy != nil { + bfPolicy = bfPolicy.Merge(*sessionPolicy) + } + for _, policyName := range pb.Spec.Policies { + policy, err := GetPolicy(ctx, adminClient, policyName) + if err != nil { + klog.Error(fmt.Errorf("Invalid policy %s, ignoring: %s", policyName, err)) + continue + } + parsedPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(policy.Policy))) + if err != nil { + klog.Error(fmt.Errorf("Invalid policy, '%s' isnot parseable ignoring: %s", policyName, err)) + continue + } + bfPolicy = bfPolicy.Merge(*parsedPolicy) + } + } + bfJSONPolicy, _ := json.Marshal(bfPolicy) + bfCompact, err := miniov2.CompactJSONString(string(bfJSONPolicy)) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSMalformedPolicyDocument, err) + return + } + if len(bfCompact) > 2048 { + writeSTSErrorResponse(w, true, ErrSTSPackedPolicyTooLarge, fmt.Errorf("PolicyBinding resulting policy is too long, Policy should not exceed 2048 characters, length %d", len(bfCompact))) + return + } + + durationStr := r.Form.Get(stsDurationSeconds) + var durationInSeconds int + if durationStr != "" { + duration, err := strconv.Atoi(durationStr) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid token expiry")) + return + } + + if duration < 900 || duration > 31536000 { + writeSTSErrorResponse(w, true, ErrSTSInvalidParameterValue, fmt.Errorf("Invalid token expiry: min 900s, max 31536000s")) + return + } + durationInSeconds = duration + } + + stsCredentials, err := AssumeRole(ctx, c, &tenant, bfCompact, durationInSeconds) + if err != nil { + writeSTSErrorResponse(w, true, ErrSTSInternalError, err) + return + } + + assumeRoleResponse := &AssumeRoleWithWebIdentityResponse{ + Result: WebIdentityResult{ + Credentials: Credentials{ + AccessKey: stsCredentials.AccessKeyID, + SecretKey: stsCredentials.SecretAccessKey, + SessionToken: stsCredentials.SessionToken, + }, + }, + } + + assumeRoleResponse.ResponseMetadata.RequestID = w.Header().Get(AmzRequestID) + writeSuccessResponseXML(w, xhttp.EncodeResponse(assumeRoleResponse)) +} diff --git a/pkg/controller/cluster/main-controller.go b/pkg/controller/cluster/main-controller.go index 3ac7f52bd2b..c5ff728ffa0 100644 --- a/pkg/controller/cluster/main-controller.go +++ b/pkg/controller/cluster/main-controller.go @@ -23,6 +23,7 @@ import ( "os" "os/signal" "strings" + "sync" "syscall" "time" @@ -72,6 +73,7 @@ import ( clientset "github.com/minio/operator/pkg/client/clientset/versioned" minioscheme "github.com/minio/operator/pkg/client/clientset/versioned/scheme" informers "github.com/minio/operator/pkg/client/informers/externalversions/minio.min.io/v2" + stsInformers "github.com/minio/operator/pkg/client/informers/externalversions/sts.min.io/v1alpha1" "github.com/minio/operator/pkg/resources/services" "github.com/minio/operator/pkg/resources/statefulsets" ) @@ -187,6 +189,9 @@ type Controller struct { // HTTP Upgrade server instance us *http.Server + // STS API server instance + sts *http.Server + // Client transport transport *http.Transport @@ -201,10 +206,14 @@ type Controller struct { healthCheckQueue queue.RateLimitingInterface // image being used in the operator deployment operatorImage string + + // policyBindingListerSynced returns true if the PolicyBinding shared informer + // has synced at least once. + policyBindingListerSynced cache.InformerSynced } // NewController returns a new sample controller -func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSet kubernetes.Interface, minioClientSet clientset.Interface, promClient promclientset.Interface, statefulSetInformer appsinformers.StatefulSetInformer, deploymentInformer appsinformers.DeploymentInformer, podInformer coreinformers.PodInformer, tenantInformer informers.TenantInformer, serviceInformer coreinformers.ServiceInformer, hostsTemplate, operatorVersion string) *Controller { +func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSet kubernetes.Interface, minioClientSet clientset.Interface, promClient promclientset.Interface, statefulSetInformer appsinformers.StatefulSetInformer, deploymentInformer appsinformers.DeploymentInformer, podInformer coreinformers.PodInformer, tenantInformer informers.TenantInformer, policyBindingInformer stsInformers.PolicyBindingInformer, serviceInformer coreinformers.ServiceInformer, hostsTemplate, operatorVersion string) *Controller { // Create event broadcaster // Add minio-controller types to the default Kubernetes Scheme so Events can be // logged for minio-controller types. @@ -234,25 +243,26 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe } controller := &Controller{ - podName: podName, - namespacesToWatch: namespacesToWatch, - kubeClientSet: kubeClientSet, - minioClientSet: minioClientSet, - promClient: promClient, - statefulSetLister: statefulSetInformer.Lister(), - statefulSetListerSynced: statefulSetInformer.Informer().HasSynced, - podInformer: podInformer.Informer(), - deploymentLister: deploymentInformer.Lister(), - deploymentListerSynced: deploymentInformer.Informer().HasSynced, - tenantsSynced: tenantInformer.Informer().HasSynced, - serviceLister: serviceInformer.Lister(), - serviceListerSynced: serviceInformer.Informer().HasSynced, - workqueue: queue.NewNamedRateLimitingQueue(MinIOControllerRateLimiter(), "Tenants"), - healthCheckQueue: queue.NewNamedRateLimitingQueue(MinIOControllerRateLimiter(), "TenantsHealth"), - recorder: recorder, - hostsTemplate: hostsTemplate, - operatorVersion: operatorVersion, - operatorImage: oprImg, + podName: podName, + namespacesToWatch: namespacesToWatch, + kubeClientSet: kubeClientSet, + minioClientSet: minioClientSet, + promClient: promClient, + statefulSetLister: statefulSetInformer.Lister(), + statefulSetListerSynced: statefulSetInformer.Informer().HasSynced, + podInformer: podInformer.Informer(), + deploymentLister: deploymentInformer.Lister(), + deploymentListerSynced: deploymentInformer.Informer().HasSynced, + tenantsSynced: tenantInformer.Informer().HasSynced, + serviceLister: serviceInformer.Lister(), + serviceListerSynced: serviceInformer.Informer().HasSynced, + workqueue: queue.NewNamedRateLimitingQueue(MinIOControllerRateLimiter(), "Tenants"), + healthCheckQueue: queue.NewNamedRateLimitingQueue(MinIOControllerRateLimiter(), "TenantsHealth"), + recorder: recorder, + hostsTemplate: hostsTemplate, + operatorVersion: operatorVersion, + policyBindingListerSynced: policyBindingInformer.Informer().HasSynced, + operatorImage: oprImg, } // Initialize operator webhook handlers @@ -261,6 +271,9 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe // Initialize operator HTTP upgrade server handlers controller.us = configureHTTPUpgradeServer(controller) + // Initialize STS API server handlers + controller.sts = configureSTSServer(controller) + klog.Info("Setting up event handlers") // Set up an event handler for when Tenant resources change tenantInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -276,6 +289,7 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe controller.enqueueTenant(new) }, }) + // Set up an event handler for when StatefulSet resources change. This // handler will lookup the owner of the given StatefulSet, and if it is // owned by a Tenant resource will enqueue that Tenant resource for @@ -360,22 +374,22 @@ func getSecretForTenant(tenant *miniov2.Tenant, accessKey, secretKey string) *v1 func (c *Controller) Start(threadiness int, stopCh <-chan struct{}) error { // Start the API and the Controller, but only if this pod is the leader run := func(ctx context.Context) { - // we need to make sure the API is ready before starting operator - apiServerWillStart := make(chan interface{}) - // we need to make sure the HTTP Upgrade server is ready before starting operator - upgradeServerWillStart := make(chan interface{}) - // pausing the process until console has it's TLS certificate (if enabled) - consoleTLS := make(chan interface{}) + var wg sync.WaitGroup + + // 1) we need to make sure the API server is ready before starting operator + // 2) we need to make sure the HTTP Upgrade server is ready before starting operator + wg.Add(2) + klog.Info("Waiting for API to start") + klog.Info("Waiting for Upgrade Server to start") go func() { // Request kubernetes version from Kube ApiServer apiCsrVersion := certificates.GetCertificatesAPIVersion(c.kubeClientSet) klog.Infof("Using Kubernetes CSR Version: %s", apiCsrVersion) - if isOperatorTLS() { publicCertPath, publicKeyPath := c.generateOperatorTLSCert() klog.Infof("Starting HTTPS API server") - close(apiServerWillStart) + wg.Done() certsManager, err := xcerts.NewManager(ctx, *publicCertPath, *publicKeyPath, LoadX509KeyPair) if err != nil { klog.Infof("HTTPS server ListenAndServeTLS failed: %v", err) @@ -383,13 +397,14 @@ func (c *Controller) Start(threadiness int, stopCh <-chan struct{}) error { } serverCertsManager = certsManager c.ws.TLSConfig = c.createTLSConfig(serverCertsManager) + if err := c.ws.ListenAndServeTLS("", ""); err != http.ErrServerClosed { klog.Infof("HTTPS server ListenAndServeTLS failed: %v", err) panic(err) } } else { klog.Infof("Starting HTTP API server") - close(apiServerWillStart) + wg.Done() // start server without TLS if err := c.ws.ListenAndServe(); err != http.ErrServerClosed { klog.Infof("HTTP server ListenAndServe failed: %v", err) @@ -400,19 +415,21 @@ func (c *Controller) Start(threadiness int, stopCh <-chan struct{}) error { go func() { klog.Infof("Starting HTTP Upgrade Tenant Image server") - close(upgradeServerWillStart) + wg.Done() if err := c.us.ListenAndServe(); err != http.ErrServerClosed { klog.Infof("HTTP Upgrade Tenant Image server ListenAndServe failed: %v", err) panic(err) } }() - go func() { - klog.Infof("Starting console TLS certificate setup") - if isOperatorConsoleTLS() { - klog.Infof("Console TLS enabled") + if isOperatorConsoleTLS() { + // we need to make sure has console TLS certificate (if enabled) + klog.Info("Waiting for Console TLS") + wg.Add(1) + go func() { + defer wg.Done() + klog.Infof("Console TLS enabled, starting console TLS certificate setup") err := c.recreateOperatorConsoleCertsIfRequired(ctx) - close(consoleTLS) if err != nil { panic(err) } @@ -421,27 +438,29 @@ func (c *Controller) Start(threadiness int, stopCh <-chan struct{}) error { if err != nil { klog.Errorf("Console deployment didn't restart: %s", err) } - } else { - klog.Infof("Console TLS is not enabled") - close(consoleTLS) - } - }() - - klog.Info("Waiting for API to start") - <-apiServerWillStart + }() + } else { + klog.Infof("Console TLS is not enabled") + } - klog.Info("Waiting for Upgrade Server to start") - <-upgradeServerWillStart + if IsSTSEnabled() { + // Wait for STS API to be ready before starting operator + wg.Add(1) + go func() { + defer wg.Done() + klog.Infof("STS is enabled, starting STS API certificate setup") + c.generateSTSTLSCert() + }() + } - klog.Info("Waiting for Console TLS") - <-consoleTLS + wg.Wait() // Start the informer factories to begin populating the informer caches klog.Info("Starting Tenant controller") // Wait for the caches to be synced before starting workers klog.Info("Waiting for informer caches to sync") - if ok := cache.WaitForCacheSync(stopCh, c.statefulSetListerSynced, c.deploymentListerSynced, c.tenantsSynced); !ok { + if ok := cache.WaitForCacheSync(stopCh, c.statefulSetListerSynced, c.deploymentListerSynced, c.tenantsSynced, c.policyBindingListerSynced); !ok { panic("failed to wait for caches to sync") } @@ -460,6 +479,31 @@ func (c *Controller) Start(threadiness int, stopCh <-chan struct{}) error { select {} } + // runSTS starts the STS API even if the pod is not the leader + runSTS := func(ctx context.Context) <-chan interface{} { + // stsServerWillStart is a channel for the STS Server API + stsServerWillStart := make(chan interface{}) + + go func() { + klog.Infof("Starting STS API server") + close(stsServerWillStart) + publicCertPath, publicKeyPath := c.waitSTSTLSCert() + certsManager, err := xcerts.NewManager(ctx, *publicCertPath, *publicKeyPath, LoadX509KeyPair) + if err != nil { + klog.Infof("STS HTTPS server ListenAndServeTLS failed: %v", err) + panic(err) + } + serverCertsManager = certsManager + c.sts.TLSConfig = c.createTLSConfig(serverCertsManager) + if err := c.sts.ListenAndServeTLS("", ""); err != http.ErrServerClosed { + klog.Infof("STS HTTPS server ListenAndServeTLS failed: %v", err) + panic(err) + } + }() + + return stsServerWillStart + } + // use a Go context so we can tell the leaderelection code when we // want to step down ctx, cancel := context.WithCancel(context.Background()) @@ -492,6 +536,14 @@ func (c *Controller) Start(threadiness int, stopCh <-chan struct{}) error { }, } + if IsSTSEnabled() { + klog.Info("Waiting for STS API to start") + started := runSTS(ctx) + <-started + } else { + klog.Info("STS Api server is not enabled, not starting") + } + // start the leader election code loop leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ Lock: lock, diff --git a/pkg/controller/cluster/sts.go b/pkg/controller/cluster/sts.go new file mode 100644 index 00000000000..09b19e260dd --- /dev/null +++ b/pkg/controller/cluster/sts.go @@ -0,0 +1,399 @@ +package cluster + +import ( + "context" + "encoding/xml" + "errors" + "net/http" + "os" + "sync" + "time" + + "github.com/gorilla/mux" + "github.com/minio/madmin-go/v2" + "github.com/minio/minio-go/v7/pkg/credentials" + miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2" + xhttp "github.com/minio/operator/pkg/internal" + authv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" +) + +// STS Handler constants +const ( + webIdentity = "AssumeRoleWithWebIdentity" + stsAPIVersion = "2011-06-15" + stsVersion = "Version" + stsAction = "Action" + stsPolicy = "Policy" + stsWebIdentityToken = "WebIdentityToken" + stsDurationSeconds = "DurationSeconds" + AmzRequestID = "x-amz-request-id" + // stsRoleArn = "RoleArn" +) + +const ( + // STSEnabled Env variable name to turn on and off the STS Service is enabled, disabled by default + STSEnabled = "OPERATOR_STS_ENABLED" + + // STSTLSSecretName is the name of secret created for the Operator STS TLS certs + STSTLSSecretName = "sts-tls" +) + +type contextKeyType string + +// Error codes, non exhaustive list - http://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithSAML.html +const ( + ErrSTSNone STSErrorCode = iota + ErrSTSAccessDenied + ErrSTSInvalidIdentityToken + ErrSTSMissingParameter + ErrSTSInvalidParameterValue + ErrSTSWebIdentityExpiredToken + ErrSTSClientGrantsExpiredToken + ErrSTSInvalidClientGrantsToken + ErrSTSMalformedPolicyDocument + ErrSTSInsecureConnection + ErrSTSInvalidClientCertificate + ErrSTSNotInitialized + ErrSTSUpstreamError + ErrSTSInternalError + ErrSTSIDPCommunicationError + ErrSTSPackedPolicyTooLarge +) + +type stsErrorCodeMap map[STSErrorCode]APIError + +// ReqInfo stores the request info. +// Reading/writing directly to struct requires appropriate R/W lock. +type ReqInfo struct { + RemoteHost string // Client Host/IP + Host string // Node Host/IP + UserAgent string // User Agent + RequestID string // x-amz-request-id + API string // API name + AccessKey string // Access Key + TenantNamespace string // tenant namespace + sync.RWMutex +} + +// Credentials holds access and secret keys. +type Credentials struct { + AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"` + SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"` + Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"` + SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"` + Status string `xml:"-" json:"status,omitempty"` + ParentUser string `xml:"-" json:"parentUser,omitempty"` + Groups []string `xml:"-" json:"groups,omitempty"` + Claims map[string]interface{} `xml:"-" json:"claims,omitempty"` +} + +// STSErrorCode type of error status. +type STSErrorCode int + +// APIError structure +type APIError struct { + Code string + Description string + HTTPStatusCode int +} + +// STSErrorResponse - error response format +type STSErrorResponse struct { + XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ ErrorResponse" json:"-"` + Error struct { + Type string `xml:"Type"` + Code string `xml:"Code"` + Message string `xml:"Message"` + } `xml:"Error"` + RequestID string `xml:"RequestId"` +} + +// error code to STSError structure, these fields carry respective +// descriptions for all the error responses. +var stsErrCodes = stsErrorCodeMap{ + ErrSTSAccessDenied: { + Code: "AccessDenied", + Description: "Generating temporary credentials not allowed for this request.", + HTTPStatusCode: http.StatusForbidden, + }, + ErrSTSInvalidIdentityToken: { + Code: "InvalidIdentityToken", + Description: "The web identity token that was passed could not be validated. Get a new identity token from the identity provider and then retry the request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSMissingParameter: { + Code: "MissingParameter", + Description: "A required parameter for the specified action is not supplied.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSInvalidParameterValue: { + Code: "InvalidParameterValue", + Description: "An invalid or out-of-range value was supplied for the input parameter.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSWebIdentityExpiredToken: { + Code: "ExpiredToken", + Description: "The web identity token that was passed is expired or is not valid. Get a new identity token from the identity provider and then retry the request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSClientGrantsExpiredToken: { + Code: "ExpiredToken", + Description: "The client grants that was passed is expired or is not valid. Get a new client grants token from the identity provider and then retry the request.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSInvalidClientGrantsToken: { + Code: "InvalidClientGrantsToken", + Description: "The client grants token that was passed could not be validated by MinIO.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSMalformedPolicyDocument: { + Code: "MalformedPolicyDocument", + Description: "The request was rejected because the policy document was malformed.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSInsecureConnection: { + Code: "InsecureConnection", + Description: "The request was made over a plain HTTP connection. A TLS connection is required.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSInvalidClientCertificate: { + Code: "InvalidClientCertificate", + Description: "The provided client certificate is invalid. Retry with a different certificate.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSNotInitialized: { + Code: "STSNotInitialized", + Description: "STS API not initialized, please try again.", + HTTPStatusCode: http.StatusServiceUnavailable, + }, + ErrSTSUpstreamError: { + Code: "InternalError", + Description: "An upstream service required for this operation failed - please try again or contact an administrator.", + HTTPStatusCode: http.StatusInternalServerError, + }, + ErrSTSInternalError: { + Code: "InternalError", + Description: "We encountered an internal error generating credentials, please try again.", + HTTPStatusCode: http.StatusInternalServerError, + }, + ErrSTSIDPCommunicationError: { + Code: "IDPCommunicationError", + Description: "The request could not be fulfilled because the identity provider (IDP) that was asked to verify the incoming identity token could not be reached.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSPackedPolicyTooLarge: { + Code: "PackedPolicyTooLarge", + Description: "The request was rejected because the total packed size of the session policies and session tags combined was too large", + HTTPStatusCode: http.StatusBadRequest, + }, +} + +// AssumedRoleUser - The identifiers for the temporary security credentials that +// the operation returns. Please also see https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15/AssumedRoleUser +type AssumedRoleUser struct { + Arn string + AssumedRoleID string `xml:"AssumeRoleId"` +} + +// WebIdentityResult - Contains the response to a successful AssumeRoleWithWebIdentity +// request, including temporary credentials that can be used to make MinIO API requests. +type WebIdentityResult struct { + // The identifiers for the temporary security credentials that the operation + // returns. + AssumedRoleUser AssumedRoleUser `xml:",omitempty"` + + // The intended audience (also known as client ID) of the web identity token. + // This is traditionally the client identifier issued to the application that + // requested the client grants. + Audience string `xml:",omitempty"` + + // The temporary security credentials, which include an access key ID, a secret + // access key, and a security (or session) token. + // + // Note: The size of the security token that STS APIs return is not fixed. We + // strongly recommend that you make no assumptions about the maximum size. As + // of this writing, the typical size is less than 4096 bytes, but that can vary. + // Also, future updates to AWS might require larger sizes. + Credentials Credentials `xml:",omitempty"` + + // A percentage value that indicates the size of the policy in packed form. + // The service rejects any policy with a packed size greater than 100 percent, + // which means the policy exceeded the allowed space. + PackedPolicySize int `xml:",omitempty"` + + // The issuing authority of the web identity token presented. For OpenID Connect + // ID tokens, this contains the value of the iss field. For OAuth 2.0 id_tokens, + // this contains the value of the ProviderId parameter that was passed in the + // AssumeRoleWithWebIdentity request. + Provider string `xml:",omitempty"` + + // The unique user identifier that is returned by the identity provider. + // This identifier is associated with the Token that was submitted + // with the AssumeRoleWithWebIdentity call. The identifier is typically unique to + // the user and the application that acquired the WebIdentityToken (pairwise identifier). + // For OpenID Connect ID tokens, this field contains the value returned by the identity + // provider as the token's sub (Subject) claim. + SubjectFromWebIdentityToken string `xml:",omitempty"` +} + +// AssumeRoleWithWebIdentityResponse contains the result of successful AssumeRoleWithWebIdentity request. +type AssumeRoleWithWebIdentityResponse struct { + XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleWithWebIdentityResponse" json:"-"` + Result WebIdentityResult `xml:"AssumeRoleWithWebIdentityResult"` + ResponseMetadata struct { + RequestID string `xml:"RequestId,omitempty"` + } `xml:"ResponseMetadata,omitempty"` +} + +func configureSTSServer(c *Controller) *http.Server { + router := mux.NewRouter().SkipClean(true).UseEncodedPath() + + router.Methods(http.MethodPost). + Path(miniov2.STSEndpoint + "/{tenantNamespace}"). + HandlerFunc(c.AssumeRoleWithWebIdentityHandler) + + router.NotFoundHandler = http.NotFoundHandler() + + s := &http.Server{ + Addr: ":" + miniov2.STSDefaultPort, + Handler: router, + ReadTimeout: time.Minute, + WriteTimeout: time.Minute, + MaxHeaderBytes: 1 << 20, + } + + return s +} + +// writeSTSErrorRespone writes error headers +func writeSTSErrorResponse(w http.ResponseWriter, isErrCodeSTS bool, errCode STSErrorCode, errCtxt error) { + var err APIError + if isErrCodeSTS { + err = stsErrCodes.ToSTSErr(errCode) + } + + stsErrorResponse := STSErrorResponse{} + stsErrorResponse.Error.Code = err.Code + stsErrorResponse.RequestID = w.Header().Get(AmzRequestID) + stsErrorResponse.Error.Message = err.Description + if errCtxt != nil { + stsErrorResponse.Error.Message = errCtxt.Error() + } + switch errCode { + case ErrSTSInternalError, ErrSTSNotInitialized, ErrSTSUpstreamError: + klog.Errorf("Error:%s/%s, err:%s", err.Code, stsErrorResponse.RequestID, errCtxt) + } + encodedErrorResponse := xhttp.EncodeResponse(stsErrorResponse) + xhttp.WriteResponse(w, err.HTTPStatusCode, encodedErrorResponse, xhttp.MimeXML) +} + +func writeSuccessResponseXML(w http.ResponseWriter, response []byte) { + xhttp.WriteResponse(w, http.StatusOK, response, xhttp.MimeXML) +} + +func (e stsErrorCodeMap) ToSTSErr(errCode STSErrorCode) APIError { + apiErr, ok := e[errCode] + if !ok { + return e[ErrSTSInternalError] + } + return apiErr +} + +// GetPolicy returns a tenant Policy by Name +func GetPolicy(ctx context.Context, adminClient *madmin.AdminClient, policyName string) (*madmin.PolicyInfo, error) { + policy, err := adminClient.InfoCannedPolicyV2(ctx, policyName) + return policy, err +} + +// AssumeRole invokes the AssumeRole method in the Minio Tenant +func AssumeRole(ctx context.Context, c *Controller, tenant *miniov2.Tenant, sessionPolicy string, duration int) (*credentials.Value, error) { + client, accessKey, secretKey, err := getTenantClient(ctx, c, tenant) + if err != nil { + return nil, err + } + + host := tenant.MinIOServerEndpoint() + if host == "" { + return nil, errors.New("MinIO server host is empty") + } + + stsOptions := credentials.STSAssumeRoleOptions{ + AccessKey: accessKey, + SecretKey: secretKey, + Policy: sessionPolicy, + DurationSeconds: duration, + } + + stsAssumeRole := &credentials.STSAssumeRole{ + Client: client, + STSEndpoint: host, + Options: stsOptions, + } + + stsCredentialsResponse, err := stsAssumeRole.Retrieve() + if err != nil { + return nil, err + } + return &stsCredentialsResponse, nil +} + +// getTenantClient returns an http client that can be used to connect with the tenant +func getTenantClient(ctx context.Context, c *Controller, tenant *miniov2.Tenant) (*http.Client, string, string, error) { + tenantConfiguration, err := c.getTenantCredentials(ctx, tenant) + transport := c.getTransport() + if err != nil { + return nil, "", "", err + } + + accessKey, ok := tenantConfiguration["accesskey"] + if !ok { + return nil, "", "", errors.New("MinIO server accesskey not set") + } + + secretKey, ok := tenantConfiguration["secretkey"] + if !ok { + return nil, "", "", errors.New("MinIO server secretkey not set") + } + + client := &http.Client{ + Transport: transport, + } + return client, string(accessKey), string(secretKey), nil +} + +// ValidateServiceAccountJWT Executes a call to TokenReview API to verify if the JWT Token received from the client +// is a valid Service Account JWT Token +func (c *Controller) ValidateServiceAccountJWT(ctx *context.Context, token string) (*authv1.TokenReview, error) { + tr := authv1.TokenReview{ + Spec: authv1.TokenReviewSpec{ + Token: token, + }, + } + + tokenReviewResult, err := c.kubeClientSet.AuthenticationV1().TokenReviews().Create(*ctx, &tr, metav1.CreateOptions{}) + if err != nil { + klog.Fatalf("Error building Kubernetes clientset: %s", err.Error()) + return nil, err + } + + return tokenReviewResult, nil +} + +// IsSTSEnabled Validates if the STS API is turned on, STS is disabled by default +// **WARNING** This will change and will be default to "on" in operator v5 +func IsSTSEnabled() bool { + value, set := os.LookupEnv(STSEnabled) + return (set && value == "on") +} + +// generateConsoleTLSCert Issues the Operator Console TLS Certificate +func (c *Controller) generateSTSTLSCert() (*string, *string) { + return c.generateTLSCert("sts", STSTLSSecretName, getOperatorDeploymentName()) +} + +// waitSTSTLSCert Waits for the Operator leader to issue the TLS Certificate for STS +func (c *Controller) waitSTSTLSCert() (*string, *string) { + return c.waitForCertSecretReady("sts", STSTLSSecretName) +} diff --git a/pkg/controller/cluster/tls.go b/pkg/controller/cluster/tls.go index cc5ceec5be3..927449b52b9 100644 --- a/pkg/controller/cluster/tls.go +++ b/pkg/controller/cluster/tls.go @@ -39,17 +39,43 @@ import ( "k8s.io/klog/v2" ) -// generateTLSCert Generic method to generate TLS Certificartes for different services -func (c *Controller) generateTLSCert(serviceName string, secretName string, deploymentName string) (*string, *string) { +// waitForCertSecretReady Function designed to run in a non-leader operator container to wait for the leader to issue a TLS certificate +func (c *Controller) waitForCertSecretReady(serviceName string, secretName string) (*string, *string) { ctx := context.Background() namespace := miniov2.GetNSFromFile() - csrName := getCSRName(serviceName) - // operator deployment for owner reference - operatorDeployment, err := c.kubeClientSet.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}) - if err != nil { + var publicCertPath, publicKeyPath string + + for { + tlsCertSecret, err := c.getCertificateSecret(ctx, namespace, secretName) + if err != nil { + if k8serrors.IsNotFound(err) { + klog.Infof("Waiting for the %s certificates secret to be issued", serviceName) + time.Sleep(time.Second * 10) + } else { + klog.Infof(err.Error()) + } + } else { + publicCertPath, publicKeyPath = c.writeCertSecretToFile(tlsCertSecret, serviceName) + break + } + } + + // validate certificates if they are valid, if not panic right here. + if _, err := tls.LoadX509KeyPair(publicCertPath, publicKeyPath); err != nil { panic(err) } + return &publicCertPath, &publicKeyPath +} + +// getCertificateSecret gets a TLS Certificate secret +func (c *Controller) getCertificateSecret(ctx context.Context, namespace string, secretName string) (*corev1.Secret, error) { + return c.kubeClientSet.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) +} + +// writeCertSecretToFile receives a [corev1.Secret] and save it's contain to the filesystem. +// returns publicCertPath (filesystem path to the public certificate file), publicKeyPath, (filesystem path to the private key file) +func (c *Controller) writeCertSecretToFile(tlsCertSecret *corev1.Secret, serviceName string) (string, string) { mkdirerr := os.MkdirAll(fmt.Sprintf("/tmp/%s", serviceName), 0o777) if mkdirerr != nil { panic(mkdirerr) @@ -57,13 +83,47 @@ func (c *Controller) generateTLSCert(serviceName string, secretName string, depl publicCertPath := fmt.Sprintf("/tmp/%s/public.crt", serviceName) publicKeyPath := fmt.Sprintf("/tmp/%s/private.key", serviceName) + publicCertKey, privateKeyKey := c.getKeyNames(tlsCertSecret) + + if val, ok := tlsCertSecret.Data[publicCertKey]; ok { + err := os.WriteFile(publicCertPath, val, 0o644) + if err != nil { + panic(err) + } + } else { + panic(fmt.Errorf("missing '%s' in %s/%s", publicCertKey, tlsCertSecret.Namespace, tlsCertSecret.Name)) + } + if val, ok := tlsCertSecret.Data[privateKeyKey]; ok { + err := os.WriteFile(publicKeyPath, val, 0o644) + if err != nil { + panic(err) + } + } else { + panic(fmt.Errorf("missing '%s' in %s/%s", privateKeyKey, tlsCertSecret.Namespace, tlsCertSecret.Name)) + } + return publicCertPath, publicKeyPath +} + +// generateTLSCert Generic method to generate TLS Certificartes for different services +func (c *Controller) generateTLSCert(serviceName string, secretName string, deploymentName string) (*string, *string) { + ctx := context.Background() + namespace := miniov2.GetNSFromFile() + csrName := getCSRName(serviceName) + var publicCertPath, publicKeyPath string + // operator deployment for owner reference + operatorDeployment, err := c.kubeClientSet.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{}) + if err != nil { + panic(err) + } for { // TLS certificates - tlsCertSecret, err := c.kubeClientSet.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) + tlsCertSecret, err := c.getCertificateSecret(ctx, namespace, secretName) if err != nil { if k8serrors.IsNotFound(err) { - klog.Infof("%s TLS secret not found: %v", serviceName, err) + if k8serrors.IsNotFound(err) { + klog.Infof("%s TLS secret not found: %v", secretName, err) + } if err = c.checkAndCreateCSR(ctx, operatorDeployment, serviceName, csrName, secretName); err != nil { klog.Infof("Waiting for the %s certificates to be issued %v", serviceName, err.Error()) time.Sleep(time.Second * 10) @@ -75,23 +135,7 @@ func (c *Controller) generateTLSCert(serviceName string, secretName string, depl } } } else { - publicCertKey, privateKeyKey := c.getKeyNames(tlsCertSecret) - if val, ok := tlsCertSecret.Data[publicCertKey]; ok { - err := os.WriteFile(publicCertPath, val, 0o644) - if err != nil { - panic(err) - } - } else { - panic(fmt.Errorf("missing '%s' in %s/%s", publicCertKey, tlsCertSecret.Namespace, tlsCertSecret.Name)) - } - if val, ok := tlsCertSecret.Data[privateKeyKey]; ok { - err := os.WriteFile(publicKeyPath, val, 0o644) - if err != nil { - panic(err) - } - } else { - panic(fmt.Errorf("missing '%s' in %s/%s", publicCertKey, tlsCertSecret.Namespace, tlsCertSecret.Name)) - } + publicCertPath, publicKeyPath = c.writeCertSecretToFile(tlsCertSecret, serviceName) break } } diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 8999fad0b70..50bdb60aa0c 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -138,6 +138,7 @@ func StartOperator() { kubeInformerFactory.Apps().V1().Deployments(), kubeInformerFactory.Core().V1().Pods(), minioInformerFactory.Minio().V2().Tenants(), + minioInformerFactory.Sts().V1alpha1().PolicyBindings(), kubeInformerFactory.Core().V1().Services(), hostsTemplate, version, diff --git a/pkg/internal/http.go b/pkg/internal/http.go new file mode 100644 index 00000000000..3b4b3513d0e --- /dev/null +++ b/pkg/internal/http.go @@ -0,0 +1,182 @@ +package http + +import ( + "bytes" + "encoding/xml" + "fmt" + "net" + "net/http" + "net/url" + "path" + "regexp" + "strconv" + "strings" + + "k8s.io/klog/v2" +) + +var ( + // De-facto standard header keys. + xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") + xRealIP = http.CanonicalHeaderKey("X-Real-IP") + // RFC7239 defines a new "Forwarded: " header designed to replace the + // existing use of X-Forwarded-* headers. + // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43 + forwarded = http.CanonicalHeaderKey("Forwarded") + // Allows for a sub-match of the first value after 'for=' to the next + // comma, semi-colon or space. The match is case-insensitive. + forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)(.*)`) +) + +// Standard S3 HTTP response constants +const ( + LastModified = "Last-Modified" + Date = "Date" + ETag = "ETag" + ContentType = "Content-Type" + ContentMD5 = "Content-Md5" + ContentEncoding = "Content-Encoding" + Expires = "Expires" + ContentLength = "Content-Length" + ContentLanguage = "Content-Language" + ContentRange = "Content-Range" + Connection = "Connection" + AcceptRanges = "Accept-Ranges" + AmzBucketRegion = "X-Amz-Bucket-Region" + ServerInfo = "Server" + RetryAfter = "Retry-After" + Location = "Location" + CacheControl = "Cache-Control" + ContentDisposition = "Content-Disposition" + Authorization = "Authorization" + Action = "Action" + Range = "Range" +) + +// mimeType represents various MIME type used API responses. +type mimeType string + +const ( + // MimeNone Means no response type. + MimeNone mimeType = "" + // MimeJSON Means response type is JSON. + MimeJSON mimeType = "application/json" + // MimeXML Means response type is XML. + MimeXML mimeType = "application/xml" +) + +// UnescapeQueryPath URL unencode the path +func UnescapeQueryPath(ep string) (string, error) { + ep, err := url.QueryUnescape(ep) + if err != nil { + return "", err + } + return TrimLeadingSlash(ep), nil +} + +// TrimLeadingSlash Cleans and ensure there is a leading slash path in the URL +func TrimLeadingSlash(ep string) string { + if len(ep) > 0 && ep[0] == '/' { + // Path ends with '/' preserve it + if ep[len(ep)-1] == '/' && len(ep) > 1 { + ep = path.Clean(ep) + ep += "/" + } else { + ep = path.Clean(ep) + } + ep = ep[1:] + } + return ep +} + +// GetSourceIPFromHeaders retrieves the IP from the X-Forwarded-For, X-Real-IP +// and RFC7239 Forwarded headers (in that order) +func GetSourceIPFromHeaders(r *http.Request) string { + var addr string + + if fwd := r.Header.Get(xForwardedFor); fwd != "" { + // Only grab the first (client) address. Note that '192.168.0.1, + // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after + // the first may represent forwarding proxies earlier in the chain. + s := strings.Index(fwd, ", ") + if s == -1 { + s = len(fwd) + } + addr = fwd[:s] + } else if fwd := r.Header.Get(xRealIP); fwd != "" { + // X-Real-IP should only contain one IP address (the client making the + // request). + addr = fwd + } else if fwd := r.Header.Get(forwarded); fwd != "" { + // match should contain at least two elements if the protocol was + // specified in the Forwarded header. The first element will always be + // the 'for=' capture, which we ignore. In the case of multiple IP + // addresses (for=8.8.8.8, 8.8.4.4, 172.16.1.20 is valid) we only + // extract the first, which should be the client IP. + if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 { + // IPv6 addresses in Forwarded headers are quoted-strings. We strip + // these quotes. + addr = strings.Trim(match[1], `"`) + } + } + + if addr != "" { + return addr + } + // Default to remote address if headers not set. + addr, _, _ = net.SplitHostPort(r.RemoteAddr) + if strings.ContainsRune(addr, ':') { + return "[" + addr + "]" + } + return addr +} + +// EncodeResponse Encodes the response headers into XML format. +func EncodeResponse(response interface{}) []byte { + var bytesBuffer bytes.Buffer + bytesBuffer.WriteString(xml.Header) + e := xml.NewEncoder(&bytesBuffer) + e.Encode(response) + return bytesBuffer.Bytes() +} + +// ParseForm Parses form fields +func ParseForm(r *http.Request) error { + if err := r.ParseForm(); err != nil { + return err + } + for k, v := range r.PostForm { + if _, ok := r.Form[k]; !ok { + r.Form[k] = v + } + } + return nil +} + +// WriteResponse writes ressponse to http.ResponseWriter +func WriteResponse(w http.ResponseWriter, statusCode int, response []byte, mType mimeType) { + if statusCode == 0 { + statusCode = 200 + } + // Similar check to http.checkWriteHeaderCode + if statusCode < 100 || statusCode > 999 { + klog.Errorf(fmt.Sprintf("invalid WriteHeader code %v", statusCode)) + statusCode = http.StatusInternalServerError + } + SetCommonHeaders(w) + if mType != MimeNone { + w.Header().Set(ContentType, string(mType)) + } + w.Header().Set(ContentLength, strconv.Itoa(len(response))) + w.WriteHeader(statusCode) + if response != nil { + w.Write(response) + } +} + +// SetCommonHeaders writes http common headers +func SetCommonHeaders(w http.ResponseWriter) { + // Set the "Server" http header. + w.Header().Set(ServerInfo, "MinIO") + w.Header().Set(AcceptRanges, "bytes") +} diff --git a/release.sh b/release.sh index e16a5facf53..75b8de376e3 100755 --- a/release.sh +++ b/release.sh @@ -15,7 +15,7 @@ CONSOLE_RELEASE="${CONSOLE_RELEASE:1}" # Figure out the FROM console release we are updating from CONSOLE_CURRENT_RELEASE=$(grep -Eo 'minio\/console:v([0-9]?[0-9].[0-9]?[0-9].[0-9]?[0-9])' resources/base/console-ui.yaml | grep -Eo '([0-9]?[0-9].[0-9]?[0-9].[0-9]?[0-9])') -files=("docs/crd.adoc" "docs/templates/asciidoctor/gv_list.tpl" "examples/kustomization/base/tenant.yaml" "helm/operator/Chart.yaml" "helm/operator/values.yaml" "helm/tenant/Chart.yaml" "helm/tenant/values.yaml" "kubectl-minio/README.md" "kubectl-minio/cmd/helpers/constants.go" "kubectl-minio/cmd/tenant-upgrade.go" "pkg/apis/minio.min.io/v2/constants.go" "resources/base/deployment.yaml" "update-operator-krew.py" "resources/base/console-ui.yaml") +files=("docs/tenant_crd.adoc" "docs/policybinding_crd.adoc" "docs/templates/asciidoctor/gv_list.tpl" "examples/kustomization/base/tenant.yaml" "helm/operator/Chart.yaml" "helm/operator/values.yaml" "helm/tenant/Chart.yaml" "helm/tenant/values.yaml" "kubectl-minio/README.md" "kubectl-minio/cmd/helpers/constants.go" "kubectl-minio/cmd/tenant-upgrade.go" "pkg/apis/minio.min.io/v2/constants.go" "resources/base/deployment.yaml" "update-operator-krew.py" "resources/base/console-ui.yaml") CURRENT_RELEASE=$(get_latest_release minio/operator) CURRENT_RELEASE="${CURRENT_RELEASE:1}" diff --git a/resources/base/cluster-role.yaml b/resources/base/cluster-role.yaml index 2ff0468aed7..bc3fee66b01 100644 --- a/resources/base/cluster-role.yaml +++ b/resources/base/cluster-role.yaml @@ -130,8 +130,15 @@ rules: verbs: - approve - sign + - apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create - apiGroups: - minio.min.io + - sts.min.io resources: - "*" verbs: diff --git a/resources/base/crds/kustomization.yaml b/resources/base/crds/kustomization.yaml new file mode 100644 index 00000000000..8d240e41c29 --- /dev/null +++ b/resources/base/crds/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - minio.min.io_tenants.yaml + - sts.min.io_policybindings.yaml \ No newline at end of file diff --git a/resources/base/crds/minio.min.io_tenants.yaml b/resources/base/crds/minio.min.io_tenants.yaml index af4c11bcca1..be905ffacda 100644 --- a/resources/base/crds/minio.min.io_tenants.yaml +++ b/resources/base/crds/minio.min.io_tenants.yaml @@ -694,6 +694,18 @@ spec: type: integer resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -1751,6 +1763,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -1954,13 +1978,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -2138,6 +2175,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -2748,6 +2797,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -2954,13 +3015,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -3521,6 +3595,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -4176,6 +4262,18 @@ spec: type: object resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -4433,13 +4531,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -4778,13 +4889,26 @@ spec: type: string name: type: string + namespace: + type: string required: - kind - name type: object - x-kubernetes-map-type: atomic resources: properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map limits: additionalProperties: anyOf: @@ -5504,4 +5628,4 @@ spec: served: true storage: true subresources: - status: {} \ No newline at end of file + status: {} diff --git a/resources/base/crds/sts.min.io_policybindings.yaml b/resources/base/crds/sts.min.io_policybindings.yaml new file mode 100644 index 00000000000..b01576f5bda --- /dev/null +++ b/resources/base/crds/sts.min.io_policybindings.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: policybindings.sts.min.io +spec: + group: sts.min.io + names: + kind: PolicyBinding + listKind: PolicyBindingList + plural: policybindings + shortNames: + - policybinding + singular: policybinding + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.currentState + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + application: + properties: + namespace: + type: string + serviceaccount: + type: string + required: + - namespace + - serviceaccount + type: object + policies: + items: + type: string + type: array + required: + - application + - policies + type: object + status: + properties: + currentState: + type: string + usage: + nullable: true + properties: + authotizations: + format: int64 + type: integer + type: object + required: + - currentState + - usage + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/resources/base/deployment.yaml b/resources/base/deployment.yaml index a0364add8da..5441a8d8594 100644 --- a/resources/base/deployment.yaml +++ b/resources/base/deployment.yaml @@ -37,6 +37,8 @@ spec: env: - name: MINIO_CONSOLE_TLS_ENABLE value: "off" + - name: OPERATOR_STS_ENABLED + value: "off" affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: diff --git a/resources/base/kustomization.yaml b/resources/base/kustomization.yaml index 9cba433c140..a3a265ff81b 100644 --- a/resources/base/kustomization.yaml +++ b/resources/base/kustomization.yaml @@ -3,4 +3,4 @@ kind: Kustomization resources: - - crds/minio.min.io_tenants.yaml + - crds/ \ No newline at end of file diff --git a/resources/base/service.yaml b/resources/base/service.yaml index 22d9809594f..d85af78c691 100644 --- a/resources/base/service.yaml +++ b/resources/base/service.yaml @@ -20,3 +20,21 @@ spec: selector: name: minio-operator operator: leader +--- +apiVersion: v1 +kind: Service +metadata: + name: sts # Please do not change this value + labels: + name: minio-operator + namespace: minio-operator + annotations: + service.alpha.openshift.io/serving-cert-secret-name: operator-tls # To solve "remote error: tls: bad certificate" +spec: + type: ClusterIP + ports: + - port: 4223 + targetPort: 4223 + name: https + selector: + name: minio-operator diff --git a/resources/kustomization.yaml b/resources/kustomization.yaml index 38869891dac..9ddaa92a528 100644 --- a/resources/kustomization.yaml +++ b/resources/kustomization.yaml @@ -11,7 +11,7 @@ resources: - base/service-account.yaml - base/cluster-role.yaml - base/cluster-role-binding.yaml - - base/crds/minio.min.io_tenants.yaml + - base/crds/ - base/service.yaml - base/deployment.yaml - base/console-ui.yaml diff --git a/resources/templates/olm-template.yaml b/resources/templates/olm-template.yaml index 5c4bf4a9409..562ae534312 100644 --- a/resources/templates/olm-template.yaml +++ b/resources/templates/olm-template.yaml @@ -16,6 +16,9 @@ spec: - kind: Tenant name: tenants.minio.min.io version: v2 + - kind: PolicyBinding + name: policybindings.sts.min.io + version: v1alpha1 keywords: - S3 - MinIO diff --git a/testing/common.sh b/testing/common.sh index 5d97ca50b70..a402d95757a 100644 --- a/testing/common.sh +++ b/testing/common.sh @@ -58,10 +58,10 @@ function install_operator() { # To compile current branch echo "Compiling Current Branch Operator" - (cd "${SCRIPT_DIR}/.." && TAG=minio/operator:noop make docker) # will not change your shell's current directory + (cd "${SCRIPT_DIR}/.." && TAG=minio/operator:noop try make docker) # will not change your shell's current directory echo 'start - load compiled image so we can use it later on' - kind load docker-image minio/operator:noop + try kind load docker-image minio/operator:noop echo 'end - load compiled image so we can use it later on' if [ "$1" = "helm" ]; then @@ -78,6 +78,12 @@ function install_operator() { echo "key, value for pod selector in helm test" key=app.kubernetes.io/name value=operator + elif [ "$1" = "sts" ]; then + echo "Installing Current Operator with sts enabled" + try kubectl apply -k "${SCRIPT_DIR}/../testing/sts/operator" + echo "key, value for pod selector in kustomize test" + key=name + value=minio-operator else echo "Installing Current Operator" # Created an overlay to use that image version from dev folder @@ -194,6 +200,36 @@ function wait_for_resource() { done } +function wait_for_resource_field_selector() { + # example 1 job: + # namespace="minio-tenant-1" + # codition="condition=Complete" + # selector="metadata.name=setup-bucket" + # wait_for_resource_field_selector $namespace job $condition $selector + # + # example 2 tenant: + # wait_for_resource_field_selector $namespace job $condition $selector + # condition=jsonpath='{.status.currentState}'=Initialized + # selector="metadata.name=storage-policy-binding" + # wait_for_resource_field_selector $namespace tenant $condition $selector 900s + + namespace=$1 + resourcetype=$2 + condition=$3 + fieldselector=$4 + if [ $# -ge 5 ]; then + timeout="$5" + else + timeout="600s" + fi + + echo "Waiting for $resourcetype \"$fieldselector\" for \"$condition\" ($timeout timeout)" + kubectl wait -n "$namespace" "$resourcetype" \ + --for=$condition \ + --field-selector $fieldselector \ + --timeout="$timeout" +} + function check_tenant_status() { # Check MinIO is accessible key=v1.min.io/tenant @@ -278,6 +314,13 @@ function install_tenant() { echo "Installing lite tenant from current branch" try kubectl apply -k "${SCRIPT_DIR}/../testing/tenant-prometheus" + elif [ "$1" = "policy-binding" ]; then + namespace="minio-tenant-1" + key=v1.min.io/tenant + value=storage-policy-binding + echo "Installing policyBinding tenant from current branch" + + try kubectl apply -k "${SCRIPT_DIR}/../examples/kustomization/sts-example/tenant" elif [ -e $1 ]; then namespace="tenant-lite" key=v1.min.io/tenant @@ -310,6 +353,52 @@ function install_tenant() { } +function setup_sts_bucket() { + echo "Installing setub bucket job" + try kubectl apply -k "${SCRIPT_DIR}/../examples/kustomization/sts-example/sample-data" + namespace="minio-tenant-1" + condition="condition=Complete" + selector="metadata.name=setup-bucket" + try wait_for_resource_field_selector $namespace job $condition $selector + echo "Installing setub bucket job: DONE" +} + +function install_sts_client() { + echo "Installing sts client job for $1" + # Definition of the sdk and client to test + + OLDIFS=$IFS + IFS="-"; declare -a CLIENTARR=($1) + sdk="${CLIENTARR[0]}-${CLIENTARR[1]}" + lang="${CLIENTARR[2]}" + makefiletarget="${CLIENTARR[0]}${CLIENTARR[1]}$lang" + IFS=$OLDIFS + + # Build and load client images + echo "Building docker image for miniodev/operator-sts-example:$1" + (cd "${SCRIPT_DIR}/../examples/kustomization/sts-example/sample-clients" && try make "${makefiletarget}") + try kind load docker-image "miniodev/operator-sts-example:$1" + + client_namespace="sts-client" + tenant_namespace="minio-tenant-1" + + if [ $# -ge 2 ]; then + if [ "$2" = "cm" ]; then + echo "Setting up certmanager CA secret" + # When certmanager issues the certificates, we copy the certificate to a secret in the client namespace + try kubectl get secrets -n $tenant_namespace tenant-certmanager-tls -o=jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt + try kubectl create secret generic tenant-certmanager-tls --from-file=ca.crt -n $client_namespace + fi + fi + + echo "creating client $1" + try kubectl apply -k "${SCRIPT_DIR}/../examples/kustomization/sts-example/sample-clients/$sdk/$lang" + condition="condition=Complete" + selector="metadata.name=sts-client-example-$sdk-$lang-job" + try wait_for_resource_field_selector $client_namespace job $condition $selector 600s + echo "Installing sts client job for $1: DONE" +} + # Port forward function port_forward() { namespace=$1 diff --git a/testing/sts/operator/console-deployment.yaml b/testing/sts/operator/console-deployment.yaml new file mode 100644 index 00000000000..a031cff2d90 --- /dev/null +++ b/testing/sts/operator/console-deployment.yaml @@ -0,0 +1,11 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: console + namespace: minio-operator +spec: + template: + spec: + containers: + - name: console + image: minio/operator:noop diff --git a/testing/sts/operator/deployment.yaml b/testing/sts/operator/deployment.yaml new file mode 100644 index 00000000000..4d9efba3ec4 --- /dev/null +++ b/testing/sts/operator/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: minio-operator + namespace: minio-operator +spec: + replicas: 2 + selector: + matchLabels: + name: minio-operator + template: + metadata: + labels: + name: minio-operator + spec: + serviceAccountName: minio-operator + containers: + - name: minio-operator + image: minio/operator:noop + imagePullPolicy: IfNotPresent + env: + - name: MINIO_CONSOLE_TLS_ENABLE + value: "off" + - name: OPERATOR_STS_ENABLED + value: "on" + securityContext: + runAsNonRoot: true + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: name + operator: In + values: + - minio-operator + topologyKey: kubernetes.io/hostname diff --git a/testing/sts/operator/kustomization.yaml b/testing/sts/operator/kustomization.yaml new file mode 100644 index 00000000000..d41c4edaa38 --- /dev/null +++ b/testing/sts/operator/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +bases: + - ../../../resources + +patchesStrategicMerge: + - deployment.yaml + - console-deployment.yaml diff --git a/testing/test-policy-binding.sh b/testing/test-policy-binding.sh new file mode 100755 index 00000000000..89a4e834659 --- /dev/null +++ b/testing/test-policy-binding.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Copyright (C) 2023, MinIO, Inc. +# +# This code is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3, +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License, version 3, +# along with this program. If not, see + +# This script requires: kubectl, kind + +SCRIPT_DIR=$(dirname "$0") +export SCRIPT_DIR + +source "${SCRIPT_DIR}/common.sh" + +function main() { + destroy_kind + + setup_kind + + install_operator "sts" + + install_tenant "policy-binding" + + check_tenant_status minio-tenant-1 storage-policy-binding + + setup_sts_bucket + + #install_sts_client "minio-sdk-dotnet" + + install_sts_client "minio-sdk-go" + + #install_sts_client "minio-sdk-java" + + # install_sts_client "minio-sdk-javascript" + + install_sts_client "minio-sdk-python" + + install_sts_client "aws-sdk-python" + + destroy_kind +} + +main "$@"