diff --git a/trusted-profiles/README.md b/trusted-profiles/README.md new file mode 100644 index 000000000..ba52b6f0c --- /dev/null +++ b/trusted-profiles/README.md @@ -0,0 +1,184 @@ +# Trusted Profiles + +In the IBM Cloud, when authenticating with other services such as Cloud +Object Storage or Secrets Manager, using trusted profiles is a way to +authenticate without any API keys being used. This eliminates the risk of +those being leaked or stolen by a malicious user who uses them to access your +IBM Cloud resources. + +You can read more about trusted profiles in the +[IBM Cloud documentation](https://cloud.ibm.com/docs/account?topic=account-create-trusted-profile). + +The samples in this directory show how to use the trusted profile in your code +using the IBM Cloud SDK that exists for Go, Java, Node, and Python. + +If you are using a different programming language, then you can still use +trusted profiles, but must implement the interaction with +[IAM to retrieve an access token for a compute resource token](https://cloud.ibm.com/apidocs/iam-identity-token-api#gettoken-crtoken) +yourself. + +Each of the samples then uses the token to list the files in a Cloud Object +Storage bucket. + +Following are the steps to run the sample. Please note that the sample +requires account permissions to create various resources including those +related to IAM permissions. + +## Setup + +To setup the example, you need three things: + +1. A Code Engine project +2. A Cloud Object Storage bucket +3. A trusted profile that grants access to the Cloud Object Storage bucket + +### Creating a Code Engine project + +We are using the command line interface here to setup things. If you have not +used it before, you can directly run it through the +[IBM Cloud shell](https://cloud.ibm.com/shell). + +When running locally, make sure the necessary plugin is installed: + +```sh +ibmcloud plugin install code-engine +``` + +In the IBM Cloud shell, the plugin is already installed, however it makes +sense to ensure the latest version is installed: + +```sh +ibmcloud plugin update --all --force +``` + +Then you are ready to create a Code Engine project. Here and in the following +snippets, variables will be used. Feel free to adjust them to your needs, but +make sure that you use the same value for the same variable in all snippets. + +```sh +REGION=eu-es +RESOURCE_GROUP=Default +CE_PROJECT_NAME=trusted-profiles-test + +ibmcloud target -r ${REGION} -g ${RESOURCE_GROUP} +ibmcloud ce project create --name ${CE_PROJECT_NAME} +``` + +### Creating a Cloud Object Storage bucket + +For this sample, you can use an existing Cloud Object Storage bucket that +you already have. If you never used COS, then here is a one-sentence +introduction: Cloud Object Storage is a managed data service where you can +store data in files. + +With the following commands, you will setup your first COS instance and a +bucket. First, make sure the CLI plugin is installed: + +```sh +ibmcloud plugin install cos +``` + +The COS bucket uses a random suffix (`31292`) because bucket names must +be unique across all IBM Cloud customers in a region. Make sure you use +your own random characters. + +```sh +REGION=eu-es +RESOURCE_GROUP=Default +COS_INSTANCE_NAME=my-first-cos +COS_BUCKET=my-first-bucket-31292 + +ibmcloud resource service-instance-create ${COS_INSTANCE_NAME} cloud-object-storage standard global -g ${RESOURCE_GROUP} -d premium-global-deployment-iam +COS_INSTANCE_ID=$(ibmcloud resource service-instance ${COS_INSTANCE_NAME} --crn 2>/dev/null | grep ':cloud-object-storage:') +ibmcloud cos config crn --crn ${COS_INSTANCE_ID} --force +ibmcloud cos bucket-create --bucket ${COS_BUCKET} --class smart --ibm-service-instance-id ${COS_INSTANCE_ID} --region ${REGION} +``` + +To have content in the bucket, let's store a sample text file: + +```sh +echo Hello World >helloworld.txt +ibmcloud cos object-put --region ${REGION} --bucket ${COS_BUCKET} --key helloworld.txt --body helloworld.txt +``` + +### Creating a trusted profile that grants a Code Engine Job access to your COS bucket + +In this step, we are creating a Trusted Profile which grants read access to +your COS bucket to a Job called `list-cos-files` in your Code Engine project. + +The Job itself, we will create later. + +```sh +REGION=eu-es +RESOURCE_GROUP=Default +COS_INSTANCE_NAME=my-first-cos +COS_BUCKET=my-first-bucket-31292 +CE_PROJECT_NAME=trusted-profiles-test +JOB_NAME=list-cos-files +TRUSTED_PROFILE_NAME=code-engine-cos-access + +CE_PROJECT_CRN=$(ibmcloud resource service-instance ${CE_PROJECT_NAME} --location ${REGION} -g ${RESOURCE_GROUP} --crn 2>/dev/null | grep ':codeengine:') +COS_INSTANCE_ID=$(ibmcloud resource service-instance ${COS_INSTANCE_NAME} --crn 2>/dev/null | grep ':cloud-object-storage:') + +ibmcloud iam trusted-profile-create ${TRUSTED_PROFILE_NAME} +ibmcloud iam trusted-profile-link-create ${TRUSTED_PROFILE_NAME} --name ce-job-${JOB_NAME} --cr-type CE --link-crn ${CE_PROJECT_CRN} --link-component-type job --link-component-name ${JOB_NAME} +ibmcloud iam trusted-profile-policy-create ${TRUSTED_PROFILE_NAME} --roles "Content Reader" --service-name cloud-object-storage --service-instance ${COS_INSTANCE_ID} --resource-type bucket --resource ${COS_BUCKET} +``` + +## Running the sample + +To run the sample, we will now create the Code Engine Job pointing to the +sources in this repository. Feel free to use `go`, `java`, `node` or +`python` as value for the `PROGRAMMING_LANGUAGE` variable. + +```sh +REGION=eu-es +COS_BUCKET=my-first-bucket-31292 +JOB_NAME=list-cos-files +PROGRAMMING_LANGUAGE=node +TRUSTED_PROFILE_NAME=code-engine-cos-access + +ibmcloud ce job create --name ${JOB_NAME} \ + --build-source https://github.com/IBM/CodeEngine \ + --build-context-dir trusted-profiles/${PROGRAMMING_LANGUAGE} \ + --trusted-profiles-enabled true \ + --env COS_REGION=${REGION} \ + --env COS_BUCKET=${COS_BUCKET} \ + --env TRUSTED_PROFILE_NAME=${TRUSTED_PROFILE_NAME} +``` + +Code Engine will setup the Job and as part of that runs a build of the chosen +source. The output is a container image that it pushes to a Container Registry +namespace that it creates for your project. Once that completed, your Job is +ready to be run: + +```sh +JOB_NAME=list-cos-files + +ibmcloud ce jobrun submit --job ${JOB_NAME} --name ${JOB_NAME}-run-1 +ibmcloud ce jobrun logs --name ${JOB_NAME}-run-1 --follow +``` + +If everything has been setup correctly, the JobRun logs will show the number +of items it found and their keys, in case you set up the sample bucket above, +then it will be just one item called helloworld.txt. + +## Code Review + +All of the four samples use the language-specific IBM Cloud SDK. Those define +an `Authenticator` interface and provide the `ContainerAuthenticator` +implementation. When instantiating the `ContainerAuthenticator`, you must +provide the identifier or name of trusted profile. The sample job from above +uses the name which is defined as `TRUSTED_PROFILE_NAME` environment variable +on the Code Engine Job. + +The `Authenticator` has an `authenticate` method that augments an existing +HTTP request object with the necessary `Authorization` header. Under the +covers, the `ContainerAuthenticator` for that purpose reaches out to +[IAM to retrieve an access token for a compute resource token](https://cloud.ibm.com/apidocs/iam-identity-token-api#gettoken-crtoken). + +The `ContainerAuthenticator` will also automatically manage the refresh of the +token. + +The sample code authenticates a request against the Cloud Object Storage API +to list the items in a bucket. The response is parsed and printed. diff --git a/trusted-profiles/go/build b/trusted-profiles/go/build new file mode 100755 index 000000000..cfe5dc505 --- /dev/null +++ b/trusted-profiles/go/build @@ -0,0 +1,15 @@ +#!/bin/bash + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +set -ex +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build and push the image +KO_DOCKER_REPO="${REGISTRY}/trusted-profiles/go" ko build . --bare --image-user 1001 --platform linux/amd64 --sbom=none diff --git a/trusted-profiles/go/go.mod b/trusted-profiles/go/go.mod new file mode 100644 index 000000000..4b217ba6d --- /dev/null +++ b/trusted-profiles/go/go.mod @@ -0,0 +1,28 @@ +module github.com/IBM/CodeEngine/trusted-profiles/go + +go 1.23.0 + +require github.com/IBM/go-sdk-core/v5 v5.19.0 + +require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.25.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect + github.com/stretchr/testify v1.10.0 // indirect + go.mongodb.org/mongo-driver v1.17.3 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/trusted-profiles/go/go.sum b/trusted-profiles/go/go.sum new file mode 100644 index 000000000..ddf36abaa --- /dev/null +++ b/trusted-profiles/go/go.sum @@ -0,0 +1,77 @@ +github.com/IBM/go-sdk-core/v5 v5.19.0 h1:YN2S5JUvq/EwYulmcNFwgyYBxZhVWl9nkY22H7Hpghw= +github.com/IBM/go-sdk-core/v5 v5.19.0/go.mod h1:deZO1J5TSlU69bCnl/YV7nPxFZA2UEaup7cq/7ZTOgw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w= +github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE= +github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= +github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= +go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/trusted-profiles/go/main.go b/trusted-profiles/go/main.go new file mode 100644 index 000000000..8e2772888 --- /dev/null +++ b/trusted-profiles/go/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "encoding/xml" + "fmt" + "io" + "log" + "net/http" + "os" + + "github.com/IBM/go-sdk-core/v5/core" +) + +type content struct { + Key string `xml:"Key"` +} + +type listBucketResult struct { + Contents []content `xml:"Contents"` +} + +func main() { + // read environment variables + cosBucket := os.Getenv("COS_BUCKET") + if cosBucket == "" { + log.Panic("environment variable COS_BUCKET is not set") + } + cosRegion := os.Getenv("COS_REGION") + if cosRegion == "" { + log.Panic("environment variable COS_REGION is not set") + } + trustedProfileName := os.Getenv("TRUSTED_PROFILE_NAME") + if trustedProfileName == "" { + log.Panic("environment variable TRUSTED_PROFILE_NAME is not set") + } + + // create an authenticator based on a trusted profile + authenticator := core.NewContainerAuthenticatorBuilder().SetIAMProfileName(trustedProfileName) + + // prepare the request to list the files in the bucket + request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://s3.direct.%s.cloud-object-storage.appdomain.cloud/%s", cosRegion, cosBucket), nil) + if err != nil { + log.Panicf("Failed to create request: %v", err) + } + + // authenticate the request + if err = authenticator.Authenticate(request); err != nil { + log.Panicf("Failed to authenticate request: %v", err) + } + + // perform the request + response, err := http.DefaultClient.Do(request) + if err != nil { + log.Panicf("Failed to perform request: %v", err) + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + log.Panicf("Unexpected status code: %d", response.StatusCode) + } + + body, err := io.ReadAll(response.Body) + if err != nil { + log.Panicf("Failed to read response body: %v", err) + } + + // parse the response which is in XML format + listBucketResult := &listBucketResult{} + if err = xml.Unmarshal(body, listBucketResult); err != nil { + log.Panicf("Failed to parse response body: %v", err) + } + + // print the details + log.Printf("Found %d objects:", len(listBucketResult.Contents)) + for _, item := range listBucketResult.Contents { + log.Printf("- %s", item.Key) + } +} diff --git a/trusted-profiles/java/.dockerignore b/trusted-profiles/java/.dockerignore new file mode 100644 index 000000000..ba4a1025a --- /dev/null +++ b/trusted-profiles/java/.dockerignore @@ -0,0 +1,5 @@ +.dockerignore +.gitignore +build +Dockerfile +target diff --git a/trusted-profiles/java/.gitignore b/trusted-profiles/java/.gitignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/trusted-profiles/java/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/trusted-profiles/java/Dockerfile b/trusted-profiles/java/Dockerfile new file mode 100644 index 000000000..5caaa427f --- /dev/null +++ b/trusted-profiles/java/Dockerfile @@ -0,0 +1,16 @@ +# Download dependencies and compile in builder stage +FROM registry.access.redhat.com/ubi9/openjdk-21 AS builder + +COPY --chown=${UID} . /src +WORKDIR /src +RUN mvn package -Dmaven.test.skip=true + +# Build final stage +FROM gcr.io/distroless/java21 + +COPY --chown=1001:0 --from=builder /src/target/trustedprofiles-1.0-SNAPSHOT.jar /app/trustedprofiles-1.0-SNAPSHOT.jar + +USER 1001:0 +WORKDIR /app + +CMD ["trustedprofiles-1.0-SNAPSHOT.jar"] diff --git a/trusted-profiles/java/build b/trusted-profiles/java/build new file mode 100755 index 000000000..ff6dfe9a5 --- /dev/null +++ b/trusted-profiles/java/build @@ -0,0 +1,19 @@ +#!/bin/bash + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# NOCACHE: set this to "--no-cache" to turn off the Docker build cache +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +set -ex +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build the image +docker build ${NOCACHE} -t ${REGISTRY}/trusted-profiles/java . --platform linux/amd64 + +# And push it +docker push ${REGISTRY}/trusted-profiles/java diff --git a/trusted-profiles/java/pom.xml b/trusted-profiles/java/pom.xml new file mode 100644 index 000000000..f007c2141 --- /dev/null +++ b/trusted-profiles/java/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + com.ibm.cloud.codeengine.sample + trustedprofiles + 1.0-SNAPSHOT + + trustedprofiles + https://github.com/src2img/ce-trusted-profiles-usage + + + UTF-8 + 21 + + + + + com.ibm.cloud + sdk-core + 9.23.0 + + + + + + + + maven-clean-plugin + 3.4.0 + + + + maven-compiler-plugin + 3.13.0 + + + maven-jar-plugin + 3.4.2 + + + + + + + maven-shade-plugin + 3.6.0 + + false + + + com.ibm.cloud.codeengine.sample.App + + + + + + package + + shade + + + + + + + diff --git a/trusted-profiles/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java b/trusted-profiles/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java new file mode 100644 index 000000000..b4922717a --- /dev/null +++ b/trusted-profiles/java/src/main/java/com/ibm/cloud/codeengine/sample/App.java @@ -0,0 +1,80 @@ +package com.ibm.cloud.codeengine.sample; + +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.ibm.cloud.sdk.core.security.Authenticator; +import com.ibm.cloud.sdk.core.security.ContainerAuthenticator; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class App { + + public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException, XPathExpressionException { + // read environment variables + String cosBucket = System.getenv("COS_BUCKET"); + if (cosBucket == null) { + System.err.println("environment variable COS_BUCKET is not set"); + System.exit(1); + } + + String cosRegion = System.getenv("COS_REGION"); + if (cosRegion == null) { + System.err.println("environment variable COS_REGION is not set"); + System.exit(1); + } + + String trustedProfileName = System.getenv("TRUSTED_PROFILE_NAME"); + if (trustedProfileName == null) { + System.err.println("environment variable TRUSTED_PROFILE_NAME is not set"); + System.exit(1); + } + + // create an authenticator based on a trusted profile + Authenticator authenticator = new ContainerAuthenticator.Builder().iamProfileName(trustedProfileName).build(); + + // prepare the request to list the files in the bucket + Request.Builder requestBuilder = new Request.Builder() + .get() + .url("https://s3.direct." + cosRegion + ".cloud-object-storage.appdomain.cloud/" + cosBucket); + + // authenticate the request + authenticator.authenticate(requestBuilder); + + // perform the request + OkHttpClient client = new OkHttpClient(); + try (Response response = client.newCall(requestBuilder.build()).execute()) { + if (response.code() != 200) { + System.err.println("Unexpected status code: " + response.code()); + System.exit(1); + } + + // read the response + try (InputStream responseBody = response.body().byteStream()) { + // parse the response + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = documentBuilder.parse(responseBody); + + NodeList keys = (NodeList)XPathFactory.newInstance().newXPath().evaluate("//Contents/Key", document, XPathConstants.NODESET); + + System.out.println("Found " + keys.getLength() + " objects:"); + for (int i = 0; i < keys.getLength(); i ++) { + System.out.println("- " + keys.item(i).getTextContent()); + } + } + } + } +} diff --git a/trusted-profiles/node/.dockerignore b/trusted-profiles/node/.dockerignore new file mode 100644 index 000000000..9c8d79a34 --- /dev/null +++ b/trusted-profiles/node/.dockerignore @@ -0,0 +1,5 @@ +.dockerignore +.gitignore +build +Dockerfile +node_modules diff --git a/trusted-profiles/node/.gitignore b/trusted-profiles/node/.gitignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/trusted-profiles/node/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/trusted-profiles/node/Dockerfile b/trusted-profiles/node/Dockerfile new file mode 100644 index 000000000..7a7a68dfd --- /dev/null +++ b/trusted-profiles/node/Dockerfile @@ -0,0 +1,17 @@ +# Download dependencies in builder stage +FROM registry.access.redhat.com/ubi9/nodejs-22 AS builder + +COPY --chown=${CNB_USER_ID}:${CNB_GROUP_ID} package.json package-lock.json /app/ +WORKDIR /app +RUN npm ci --omit=dev + +# Build final stage +FROM gcr.io/distroless/nodejs22 + +COPY --chown=1001:0 --from=builder /app/node_modules /app/node_modules +COPY --chown=1001:0 main.js /app + +USER 1001:0 +WORKDIR /app + +CMD ["main.js"] diff --git a/trusted-profiles/node/build b/trusted-profiles/node/build new file mode 100755 index 000000000..5ada506ef --- /dev/null +++ b/trusted-profiles/node/build @@ -0,0 +1,19 @@ +#!/bin/bash + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# NOCACHE: set this to "--no-cache" to turn off the Docker build cache +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +set -ex +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build the image +docker build ${NOCACHE} -t ${REGISTRY}/trusted-profiles/node . --platform linux/amd64 + +# And push it +docker push ${REGISTRY}/trusted-profiles/node diff --git a/trusted-profiles/node/main.js b/trusted-profiles/node/main.js new file mode 100644 index 000000000..e42a14401 --- /dev/null +++ b/trusted-profiles/node/main.js @@ -0,0 +1,61 @@ +import { XMLParser } from 'fast-xml-parser'; +import { ContainerAuthenticator } from 'ibm-cloud-sdk-core'; + +// read environment variables +const cosBucket = process.env.COS_BUCKET; +if (!cosBucket) { + console.error('environment variable COS_BUCKET is not set'); + process.exit(1); +} + +const cosRegion = process.env.COS_REGION; +if (!cosRegion) { + console.error('environment variable COS_REGION is not set'); + process.exit(1); +} + +const trustedProfileName = process.env.TRUSTED_PROFILE_NAME; +if (!trustedProfileName) { + console.error('environment variable TRUSTED_PROFILE_NAME is not set'); + process.exit(1); +} + +// create an authenticator based on a trusted profile +const authenticator = new ContainerAuthenticator({ + iamProfileName: trustedProfileName +}); + +// prepare the request to list the files in the bucket +const requestOptions = { + method: 'GET', +}; + +// authenticate the request +await authenticator.authenticate(requestOptions); + +// perform the request +const response = await fetch(`https://s3.direct.${cosRegion}.cloud-object-storage.appdomain.cloud/${cosBucket}`, requestOptions); + +if (response.status !== 200) { + console.error(`Unexpected status code: ${response.status}`); + process.exit(1); +} + +// read the response +const responseBody = await response.text(); + +// parse the response +const parsedBody = new XMLParser().parse(responseBody); + +// the XML parser does not know when something is an array if only one item is there +const contents = []; +if (parsedBody.ListBucketResult.Contents instanceof Array) { + // multiple items + contents.push(...parsedBody.ListBucketResult.Contents) +} else if (typeof parsedBody.ListBucketResult.Contents === 'object') { + // single items + contents.push(parsedBody.ListBucketResult.Contents); +} + +console.log(`Found ${contents.length} objects:`); +contents.forEach((content) => console.log(`- ${content.Key}`)); diff --git a/trusted-profiles/node/package-lock.json b/trusted-profiles/node/package-lock.json new file mode 100644 index 000000000..1175c0bec --- /dev/null +++ b/trusted-profiles/node/package-lock.json @@ -0,0 +1,692 @@ +{ + "name": "ce-trusted-profiles-usage", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ce-trusted-profiles-usage", + "version": "1.0.0", + "license": "BSD-3-Clause", + "dependencies": { + "fast-xml-parser": "^5.0.8", + "ibm-cloud-sdk-core": "^5.3.0" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.80", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", + "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", + "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.0.8.tgz", + "integrity": "sha512-qY8NiI5L8ff00F2giyICiJxSSKHO52tC36LJqx2JtvGyAd5ZfehC/l4iUVVHpmpIa6sM9N5mneSLHQG2INGoHA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ibm-cloud-sdk-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ibm-cloud-sdk-core/-/ibm-cloud-sdk-core-5.3.2.tgz", + "integrity": "sha512-YhtS+7hGNO61h/4jNShHxbbuJ1TnDqiFKQzfEaqePnonOvv8NnxWxOk92FlKKCCzZNOT34Gnd7WCLVJTntwEFQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/debug": "^4.1.12", + "@types/node": "^18.19.80", + "@types/tough-cookie": "^4.0.0", + "axios": "^1.8.2", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "extend": "3.0.2", + "file-type": "16.5.4", + "form-data": "4.0.0", + "isstream": "0.1.2", + "jsonwebtoken": "^9.0.2", + "mime-types": "2.1.35", + "retry-axios": "^2.6.0", + "tough-cookie": "^4.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" + }, + "node_modules/retry-axios": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/retry-axios/-/retry-axios-2.6.0.tgz", + "integrity": "sha512-pOLi+Gdll3JekwuFjXO3fTq+L9lzMQGcSq7M5gIjExcl3Gu1hd4XXuf5o3+LuSBsaULQH7DiNbsqPd1chVpQGQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.7.0" + }, + "peerDependencies": { + "axios": "*" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strnum": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.0.5.tgz", + "integrity": "sha512-YAT3K/sgpCUxhxNMrrdhtod3jckkpYwH6JAuwmUdXZsmzH1wUyzTMrrK2wYCEEqlKwrWDd35NeuUkbBy/1iK+Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + } + } +} diff --git a/trusted-profiles/node/package.json b/trusted-profiles/node/package.json new file mode 100644 index 000000000..9d84f688e --- /dev/null +++ b/trusted-profiles/node/package.json @@ -0,0 +1,24 @@ +{ + "name": "ce-trusted-profiles-usage", + "version": "1.0.0", + "description": "Sample that shows how to use the ContainerAuthenticator in code that runs in IBM Cloud Code Engine.", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/IBM/CodeEngine.git" + }, + "keywords": [ + "code-engine", + "ibm-cloud", + "authentication" + ], + "license": "BSD-3-Clause", + "bugs": { + "url": "https://github.com/IBM/CodeEngine/issues" + }, + "homepage": "https://github.com/IBM/CodeEngine/blob/main/trusted-profiles/README.md", + "dependencies": { + "fast-xml-parser": "^5.0.8", + "ibm-cloud-sdk-core": "^5.3.0" + } +} diff --git a/trusted-profiles/python/.dockerignore b/trusted-profiles/python/.dockerignore new file mode 100644 index 000000000..405af2e02 --- /dev/null +++ b/trusted-profiles/python/.dockerignore @@ -0,0 +1,3 @@ +.dockerignore +build +Dockerfile diff --git a/trusted-profiles/python/Dockerfile b/trusted-profiles/python/Dockerfile new file mode 100644 index 000000000..287ca9991 --- /dev/null +++ b/trusted-profiles/python/Dockerfile @@ -0,0 +1,18 @@ +# Download dependencies in builder stage +FROM registry.access.redhat.com/ubi9/python-39 AS builder + +COPY requirements.txt . +RUN python -m pip install -r requirements.txt + +# Build final stage +FROM gcr.io/distroless/python3 + +ENV PYTHONPATH=/app/site-packages + +COPY --chown=1001:0 --from=builder /opt/app-root/lib/python3.9/site-packages ${PYTHONPATH} +COPY --chown=1001:0 main.py /app/ + +USER 1001:0 +WORKDIR /app + +CMD ["main.py"] diff --git a/trusted-profiles/python/build b/trusted-profiles/python/build new file mode 100755 index 000000000..35f5deb9e --- /dev/null +++ b/trusted-profiles/python/build @@ -0,0 +1,19 @@ +#!/bin/bash + +# Env Vars: +# REGISTRY: name of the image registry/namespace to store the images +# NOCACHE: set this to "--no-cache" to turn off the Docker build cache +# +# NOTE: to run this you MUST set the REGISTRY environment variable to +# your own image registry/namespace otherwise the `docker push` commands +# will fail due to an auth failure. Which means, you also need to be logged +# into that registry before you run it. + +set -ex +export REGISTRY=${REGISTRY:-icr.io/codeengine} + +# Build the image +docker build ${NOCACHE} -t ${REGISTRY}/trusted-profiles/python . --platform linux/amd64 + +# And push it +docker push ${REGISTRY}/trusted-profiles/python diff --git a/trusted-profiles/python/main.py b/trusted-profiles/python/main.py new file mode 100644 index 000000000..d0185fe12 --- /dev/null +++ b/trusted-profiles/python/main.py @@ -0,0 +1,57 @@ +import os +import requests +import xmltodict + +from ibm_cloud_sdk_core.authenticators import ContainerAuthenticator + +if __name__ == '__main__': + # read environment variables + cosBucket = os.getenv('COS_BUCKET') + if cosBucket is None: + print('environment variable COS_BUCKET is not set') + exit(1) + + cosRegion = os.getenv('COS_REGION') + if cosRegion is None: + print('environment variable COS_REGION is not set') + exit(1) + + trustedProfileName = os.getenv('TRUSTED_PROFILE_NAME') + if trustedProfileName is None: + print('environment variable TRUSTED_PROFILE_NAME is not set') + exit(1) + + # create an authenticator based on a trusted profile + authenticator = ContainerAuthenticator(iam_profile_name=trustedProfileName) + + # prepare the request to list the files in the bucket + request = { + 'method': 'GET', + 'url': 'https://s3.direct.{cosRegion}.cloud-object-storage.appdomain.cloud/{cosBucket}'.format(cosRegion=cosRegion, cosBucket=cosBucket), + 'headers': {} + } + + # authenticate the request + authenticator.authenticate(request) + + # perform the request + response = requests.request(**request) + if response.status_code != 200: + print('Unexpected status code: {status}'.format(status=response.status_code)) + exit(1) + + # parse the response + parsedBody = xmltodict.parse(response.content) + + # the XML parser does not know when something is an array if only one item is there + contents = [] + if type(parsedBody['ListBucketResult']['Contents']) is list: + # multiple items + contents = parsedBody['ListBucketResult']['Contents'] + elif type(parsedBody['ListBucketResult']['Contents']) is dict: + # single items + contents.append(parsedBody['ListBucketResult']['Contents']) + + print('Found {length} objects:'.format(length=len(contents))) + for content in contents: + print('- {item}'.format(item=content['Key'])) diff --git a/trusted-profiles/python/requirements.txt b/trusted-profiles/python/requirements.txt new file mode 100644 index 000000000..046316818 --- /dev/null +++ b/trusted-profiles/python/requirements.txt @@ -0,0 +1,3 @@ +ibm_cloud_sdk_core==3.23.0 +requests==2.32.3 +xmltodict==0.14.2