├── .buildspec.yaml
├── .dockerignore
├── .github
└── workflows
│ └── dependabot.yaml
├── .gitignore
├── .gitlab-ci.yml
├── .idea
├── cfn-saml-provider.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── .make-release-support
├── .pep8.rc
├── .release
├── Dockerfile
├── LICENSE
├── Makefile
├── Makefile.mk
├── Pipfile
├── Pipfile.lock
├── README.md
├── cloudformation
├── cfn-resource-provider.yaml
├── cicd-pipeline.yaml
└── demo-stack.yaml
├── docs
└── saml-provider.md
├── requirements.txt
├── src
├── __init__.py
└── saml_provider.py
├── test-requirements.txt
└── tests
└── test_saml_provider.py
/.buildspec.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 0.2
3 | phases:
4 | install:
5 | runtime-versions:
6 | python: 3.9
7 | build:
8 | commands:
9 | - make ecr-login
10 | - make snapshot
11 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | target/
2 | db.sqllite3
3 | mysql-data/
4 | .git/
5 | staticfiles/
6 | venv/
7 |
--------------------------------------------------------------------------------
/.github/workflows/dependabot.yaml:
--------------------------------------------------------------------------------
1 |
2 | name: Merge dependabot PRs
3 |
4 | on: pull_request_target
5 |
6 | permissions:
7 | pull-requests: write
8 | contents: write
9 |
10 | jobs:
11 | dependabot:
12 | runs-on: ubuntu-latest
13 | if: ${{ github.actor == 'dependabot[bot]' }}
14 | steps:
15 | - name: Dependabot metadata
16 | id: dependabot-metadata
17 | uses: dependabot/fetch-metadata@v1
18 | with:
19 | github-token: ${{ secrets.GITHUB_TOKEN }}
20 | - name: Approve a PR
21 | run: gh pr review --approve "$PR_URL"
22 | env:
23 | PR_URL: ${{ github.event.pull_request.html_url }}
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | - name: Enable auto-merge for Dependabot PRs
26 | if: ${{ steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' }}
27 | run: gh pr merge --auto --squash "$PR_URL"
28 | env:
29 | PR_URL: ${{ github.event.pull_request.html_url }}
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .pytest_cache/
2 | tests/.pytest_cache/
3 | .idea/workspace.xml
4 | .idea/tasks.xml
5 | .idea/dbnavigator.xml
6 | .noseids
7 | *.zip
8 | deps/
9 | .vscode/
10 | # Byte-compiled / optimized / DLL files
11 | __pycache__/
12 | *.py[cod]
13 | *$py.class
14 |
15 | # C extensions
16 | *.so
17 |
18 | # Distribution / packaging
19 | .Python
20 | env/
21 | build/
22 | develop-eggs/
23 | dist/
24 | downloads/
25 | eggs/
26 | .eggs/
27 | lib/
28 | lib64/
29 | parts/
30 | sdist/
31 | var/
32 | wheels/
33 | *.egg-info/
34 | .installed.cfg
35 | *.egg
36 |
37 | # PyInstaller
38 | # Usually these files are written by a python script from a template
39 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
40 | *.manifest
41 | *.spec
42 |
43 | # Installer logs
44 | pip-log.txt
45 | pip-delete-this-directory.txt
46 |
47 | # Unit test / coverage reports
48 | htmlcov/
49 | .tox/
50 | .coverage
51 | .coverage.*
52 | .cache
53 | nosetests.xml
54 | coverage.xml
55 | *.cover
56 | .hypothesis/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # pyenv
83 | .python-version
84 |
85 | # celery beat schedule file
86 | celerybeat-schedule
87 |
88 | # SageMath parsed files
89 | *.sage.py
90 |
91 | # dotenv
92 | .env
93 |
94 | # virtualenv
95 | .venv
96 | venv/
97 | ENV/
98 |
99 | # Spyder project settings
100 | .spyderproject
101 | .spyproject
102 |
103 | # Rope project settings
104 | .ropeproject
105 |
106 | # mkdocs documentation
107 | /site
108 |
109 | # mypy
110 | .mypy_cache/
111 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | build:
3 | stage: build
4 | script:
5 | - make S3_BUCKET_PREFIX=${LAMBDA_BUCKET_PREFIX:-binxio-public} AWS_REGION=${LAMBDA_BUCKET_REGION:-eu-central-1} deploy
6 |
--------------------------------------------------------------------------------
/.idea/cfn-saml-provider.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.make-release-support:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Xebia Nederland B.V.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | function hasChanges() {
16 | test -n "$(showChanges)"
17 | }
18 |
19 | function showChanges() {
20 | git status -s -- $(getTagOnChangesIn)
21 | }
22 |
23 | function getTagOnChangesIn() {
24 | result=$(awk -F= '/^tag_on_changes_in=/{print $2}' .release)
25 | if egrep -q -e '^\.$' -e '^\./?[ ]' -e '[ ]\./?[ ]' <<< "$result" ; then
26 | echo "$result"
27 | else
28 | echo ". $result"
29 | fi
30 | return 0
31 | }
32 |
33 | function getRelease() {
34 | awk -F= '/^release=/{print $2}' .release
35 | }
36 |
37 | function getBaseTag() {
38 | sed -n -e "s/^tag=\(.*\)$(getRelease)\$/\1/p" .release
39 | }
40 |
41 | function getTag() {
42 | if [ -z "$1" ] ; then
43 | awk -F= '/^tag/{print $2}' .release
44 | else
45 | echo "$(getBaseTag)$1"
46 | fi
47 | }
48 |
49 | function setRelease() {
50 | if [ -n "$1" ] ; then
51 | sed -i.x -e "s~^tag=.*~tag=$(getTag $1)~" .release
52 | sed -i.x -e "s~^release=.*~release=$1~g" .release
53 | rm -f .release.x
54 | runPreTagCommand "$1"
55 | else
56 | echo "ERROR: missing release version parameter " >&2
57 | return 1
58 | fi
59 | }
60 |
61 | function runPreTagCommand() {
62 | if [ -n "$1" ] ; then
63 | COMMAND=$(sed -n -e "s/@@RELEASE@@/$1/g" -e 's/^pre_tag_command=\(.*\)/\1/p' .release)
64 | if [ -n "$COMMAND" ] ; then
65 | if ! OUTPUT=$(bash -c "$COMMAND" 2>&1) ; then echo $OUTPUT >&2 && exit 1 ; fi
66 | fi
67 | else
68 | echo "ERROR: missing release version parameter " >&2
69 | return 1
70 | fi
71 | }
72 |
73 | function tagExists() {
74 | tag=${1:-$(getTag)}
75 | test -n "$tag" && test -n "$(git tag | grep "^$tag\$")"
76 | }
77 |
78 | function showDiffFromRelease() {
79 | git diff --compact-summary -r $tag -- $(getTagOnChangesIn)
80 | }
81 |
82 | function differsFromRelease() {
83 | tag=$(getTag)
84 | ! tagExists $tag || test -n "$(showDiffFromRelease)"
85 | }
86 |
87 | function getVersion() {
88 | result=$(getRelease)
89 |
90 | if differsFromRelease; then
91 | result="$result-$(git log -n 1 --format=%h $(getTagOnChangesIn))"
92 | fi
93 |
94 | if hasChanges ; then
95 | result="$result-dirty"
96 | fi
97 | echo $result
98 | }
99 |
100 | function nextPatchLevel() {
101 | version=${1:-$(getRelease)}
102 | major_and_minor=$(echo $version | cut -d. -f1,2)
103 | patch=$(echo $version | cut -d. -f3)
104 | version=$(printf "%s.%d" $major_and_minor $(($patch + 1)))
105 | echo $version
106 | }
107 |
108 | function nextMinorLevel() {
109 | version=${1:-$(getRelease)}
110 | major=$(echo $version | cut -d. -f1);
111 | minor=$(echo $version | cut -d. -f2);
112 | version=$(printf "%d.%d.0" $major $(($minor + 1))) ;
113 | echo $version
114 | }
115 |
116 | function nextMajorLevel() {
117 | version=${1:-$(getRelease)}
118 | major=$(echo $version | cut -d. -f1);
119 | version=$(printf "%d.0.0" $(($major + 1)))
120 | echo $version
121 | }
122 |
--------------------------------------------------------------------------------
/.pep8.rc:
--------------------------------------------------------------------------------
1 | [pep8]
2 | #ignore = E226,E302,E41
3 | max-line-length = 132
4 |
--------------------------------------------------------------------------------
/.release:
--------------------------------------------------------------------------------
1 | release=2.0.0
2 | tag=v2.0.0
3 | pre_tag_command=sed -i '' -e 's^cfn-saml-provider:[0-9a-z][0-9a-z._-]*^cfn-saml-provider:@@RELEASE@@^' README.md cloudformation/cfn-resource-provider.yaml
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM --platform=linux/amd64 public.ecr.aws/lambda/python:3.9
2 |
3 | WORKDIR ${LAMBDA_TASK_ROOT}
4 |
5 | COPY requirements.txt .
6 |
7 | RUN pip install -r requirements.txt
8 |
9 | COPY src/ ./
10 |
11 | RUN find . -type d -print0 | xargs -0 chmod ugo+rx && \
12 | find . -type f -print0 | xargs -0 chmod ugo+r
13 |
14 | CMD ["saml_provider.handler"]
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | include Makefile.mk
2 |
3 | USERNAME=xebia
4 | NAME=cfn-saml-provider
5 |
6 | AWS_REGION=eu-central-1
7 | AWS_ACCOUNT=$(shell aws sts get-caller-identity --query Account --output text)
8 | REGISTRY_HOST=$(AWS_ACCOUNT).dkr.ecr.$(AWS_REGION).amazonaws.com
9 | IMAGE=$(REGISTRY_HOST)/$(USERNAME)/$(NAME)
10 | TAG_WITH_LATEST=never
11 |
12 |
13 | requirements.txt test-requirements.txt: Pipfile.lock
14 | pipenv requirements > requirements.txt
15 | pipenv requirements --dev-only > test-requirements.txt
16 |
17 | Pipfile.lock: Pipfile
18 | pipenv update
19 |
20 | test: Pipfile.lock
21 | for n in ./cloudformation/*.yaml ; do aws cloudformation validate-template --template-body file://$$n ; done
22 | PYTHONPATH=$(PWD)/src pipenv run pytest ./tests/test*.py
23 |
24 | pre-build: requirements.txt
25 |
26 |
27 | fmt:
28 | black src/*.py tests/*.py
29 |
30 | deploy-provider: ## deploy the provider to the current account
31 | sed -i '' -e 's^$(NAME):[0-9]*\.[0-9]*\.[0-9]*[^\.]*^$(NAME):$(VERSION)^' cloudformation/cfn-resource-provider.yaml
32 | aws cloudformation deploy \
33 | --stack-name $(NAME) \
34 | --capabilities CAPABILITY_IAM \
35 | --template-file ./cloudformation/cfn-resource-provider.yaml
36 |
37 | delete-provider: ## delete provider from the current account
38 | aws cloudformation delete-stack --stack-name $(NAME)
39 | aws cloudformation wait stack-delete-complete --stack-name $(NAME)
40 |
41 |
42 |
43 | deploy-pipeline: ## deploy the CI/CD pipeline
44 | aws cloudformation deploy \
45 | --stack-name $(NAME)-pipeline \
46 | --capabilities CAPABILITY_IAM \
47 | --template-file ./cloudformation/cicd-pipeline.yaml \
48 | --parameter-overrides Name=$(NAME)
49 |
50 | delete-pipeline: ## delete the CI/CD pipeline
51 | aws cloudformation delete-stack --stack-name $(NAME)-pipeline
52 | aws cloudformation wait stack-delete-complete --stack-name $(NAME)-pipeline
53 |
54 | demo: ## deploy the demo
55 | aws cloudformation deploy \
56 | --stack-name $(NAME)-demo \
57 | --capabilities CAPABILITY_NAMED_IAM \
58 | --template-file ./cloudformation/demo-stack.yaml
59 |
60 | delete-demo: ## delete the demo
61 | aws cloudformation delete-stack --stack-name $(NAME)-demo
62 | aws cloudformation wait stack-delete-complete --stack-name $(NAME)-demo
63 |
64 | ecr-login: ## login to the ECR repository
65 | aws ecr get-login-password --region $(AWS_REGION) | \
66 | docker login --username AWS --password-stdin $(REGISTRY_HOST)
67 |
--------------------------------------------------------------------------------
/Makefile.mk:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2015-2024 Xebia Nederland B.V.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | REGISTRY_HOST=docker.io
17 | USERNAME=$(USER)
18 | NAME=$(shell basename $(CURDIR))
19 |
20 | RELEASE_SUPPORT := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))/.make-release-support
21 | IMAGE=$(REGISTRY_HOST)/$(USERNAME)/$(NAME)
22 |
23 |
24 | VERSION=$(shell . $(RELEASE_SUPPORT) ; getVersion)
25 | BASE_RELEASE=$(shell . $(RELEASE_SUPPORT) ; getRelease)
26 |
27 | TAG=$(shell . $(RELEASE_SUPPORT); getTag)
28 | TAG_WITH_LATEST=always
29 |
30 | SHELL=/bin/bash
31 |
32 | DOCKER_BUILD_CONTEXT=.
33 | DOCKER_FILE_PATH=Dockerfile
34 |
35 | .PHONY: pre-build docker-build post-build build release patch-release minor-release major-release tag check-status check-release showver \
36 | push pre-push do-push post-push showimage
37 |
38 | build: pre-build docker-build post-build ## builds a new version of your container image
39 |
40 | pre-build:
41 |
42 |
43 | post-build:
44 |
45 |
46 | pre-push:
47 |
48 |
49 | post-push:
50 |
51 |
52 | docker-build: .release
53 | docker build $(DOCKER_BUILD_ARGS) -t $(IMAGE):$(VERSION) $(DOCKER_BUILD_CONTEXT) -f $(DOCKER_FILE_PATH)
54 | @if [[ $(TAG_WITH_LATEST) != never ]] && ([[ $(TAG_WITH_LATEST) == always ]] || [[ $(BASE_RELEASE) == $(VERSION) ]]); then \
55 | echo docker tag $(IMAGE):$(VERSION) $(IMAGE):latest >&2; \
56 | docker tag $(IMAGE):$(VERSION) $(IMAGE):latest; \
57 | else \
58 | echo docker rmi --force --no-prune $(IMAGE):latest >&2; \
59 | docker rmi --force --no-prune $(IMAGE):latest 2>/dev/null; \
60 | fi
61 |
62 | .release:
63 | @echo "release=0.0.0" > .release
64 | @echo "tag=$(NAME)-0.0.0" >> .release
65 | @echo "tag_on_changes_in=." >> .release
66 | @echo INFO: .release created
67 | @cat .release
68 |
69 |
70 | release: check-status check-release build push
71 |
72 |
73 | push: pre-push do-push post-push
74 |
75 | do-push: IMAGE_EXISTS=$(shell docker manifest inspect $(IMAGE):$(VERSION) 2>/dev/null)
76 | do-push:
77 | docker push $(IMAGE):$(VERSION)
78 | @if [[ $(TAG_WITH_LATEST) != never ]] && ([[ $(TAG_WITH_LATEST) == always ]] || [[ $(BASE_RELEASE) == $(VERSION) ]]); then \
79 | echo docker push $(IMAGE):latest >&2; \
80 | docker push $(IMAGE):latest; \
81 | fi
82 |
83 | snapshot: build push ## builds a new version of your container image, and pushes it to the registry
84 |
85 | showver: .release ## shows the current release tag based on the workspace
86 | @. $(RELEASE_SUPPORT); getVersion
87 |
88 | showimage: .release ## shows the container image name based on the workspace
89 | @echo $(IMAGE):$(VERSION)
90 |
91 | tag-patch-release: VERSION := $(shell . $(RELEASE_SUPPORT); nextPatchLevel)
92 | tag-patch-release: .release tag ## increments the patch release level and create the tag without build
93 |
94 | tag-minor-release: VERSION := $(shell . $(RELEASE_SUPPORT); nextMinorLevel)
95 | tag-minor-release: .release tag ## increments the minor release level and create the tag without build
96 |
97 | tag-major-release: VERSION := $(shell . $(RELEASE_SUPPORT); nextMajorLevel)
98 | tag-major-release: .release tag ## increments the major release level and create the tag without build
99 |
100 | patch-release: tag-patch-release release ## increments the patch release level, build and push to registry
101 | @echo $(VERSION)
102 |
103 | minor-release: tag-minor-release release ## increments the minor release level, build and push to registry
104 | @echo $(VERSION)
105 |
106 | major-release: tag-major-release release ## increments the major release level, build and push to registry
107 | @echo $(VERSION)
108 |
109 |
110 | tag: TAG=$(shell . $(RELEASE_SUPPORT); getTag $(VERSION))
111 | tag: check-status
112 | @. $(RELEASE_SUPPORT) ; ! tagExists $(TAG) || (echo "ERROR: tag $(TAG) for version $(VERSION) already tagged in git" >&2 && exit 1) ;
113 | @. $(RELEASE_SUPPORT) ; setRelease $(VERSION)
114 | git add .
115 | git commit -m "bumped to version $(VERSION)" ;
116 | git tag $(TAG) ;
117 | @ if [ -n "$(shell git remote -v)" ] ; then git push --tags ; else echo 'no remote to push tags to' ; fi
118 |
119 | check-status: ## checks whether there are outstanding changes
120 | @. $(RELEASE_SUPPORT) ; ! hasChanges || (echo "ERROR: there are still outstanding changes" >&2 && showChanges >&2 && exit 1) ;
121 |
122 | check-release: .release ## checks whether the workspace matches the tagged release in git
123 | @. $(RELEASE_SUPPORT) ; tagExists $(TAG) || (echo "ERROR: version not yet tagged in git. make [minor,major,patch]-release." >&2 && exit 1) ;
124 | @. $(RELEASE_SUPPORT) ; ! differsFromRelease $(TAG) || (echo "ERROR: current directory differs from tagged $(TAG). make [minor,major,patch]-release." && showDiffFromRelease >&2 ; exit 1)
125 |
126 |
127 | help: ## show this help.
128 | @fgrep -h "##" $(MAKEFILE_LIST) | grep -v fgrep | sed -e 's/\([^:]*\):[^#]*##\(.*\)/printf '"'%-20s - %s\\\\n' '\1' '\2'"'/' |bash
129 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [packages]
7 | cfn_resource_provider = ">=0.9.0"
8 | requests = "*"
9 | boto3 = "*"
10 |
11 | [dev-packages]
12 | pytest = "*"
13 |
14 | [requires]
15 | python_version = "3.9"
16 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "abd5222a379d4b3a7c15172fe0a3ac0b2a286a40bf6bc8124642d57eabea0284"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.9"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "attrs": {
20 | "hashes": [
21 | "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346",
22 | "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"
23 | ],
24 | "markers": "python_version >= '3.7'",
25 | "version": "==24.2.0"
26 | },
27 | "boto3": {
28 | "hashes": [
29 | "sha256:9edf49640c79a05b0a72f4c2d1e24dfc164344b680535a645f455ac624dc3680",
30 | "sha256:db58348849a5af061f0f5ec9c3b699da5221ca83354059fdccb798e3ddb6b62a"
31 | ],
32 | "index": "pypi",
33 | "markers": "python_version >= '3.8'",
34 | "version": "==1.35.57"
35 | },
36 | "botocore": {
37 | "hashes": [
38 | "sha256:92ddd02469213766872cb2399269dd20948f90348b42bf08379881d5e946cc34",
39 | "sha256:d96306558085baf0bcb3b022d7a8c39c93494f031edb376694d2b2dcd0e81327"
40 | ],
41 | "markers": "python_version >= '3.8'",
42 | "version": "==1.35.57"
43 | },
44 | "certifi": {
45 | "hashes": [
46 | "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8",
47 | "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"
48 | ],
49 | "markers": "python_version >= '3.6'",
50 | "version": "==2024.8.30"
51 | },
52 | "cfn-resource-provider": {
53 | "hashes": [
54 | "sha256:41f5b3f41ccd208cbb8930118860af6afc21a7851f51fab251955c8f930e1ad2"
55 | ],
56 | "index": "pypi",
57 | "version": "==1.2.1"
58 | },
59 | "charset-normalizer": {
60 | "hashes": [
61 | "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621",
62 | "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6",
63 | "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8",
64 | "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912",
65 | "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c",
66 | "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b",
67 | "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d",
68 | "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d",
69 | "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95",
70 | "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e",
71 | "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565",
72 | "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64",
73 | "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab",
74 | "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be",
75 | "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e",
76 | "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907",
77 | "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0",
78 | "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2",
79 | "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62",
80 | "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62",
81 | "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23",
82 | "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc",
83 | "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284",
84 | "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca",
85 | "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455",
86 | "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858",
87 | "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b",
88 | "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594",
89 | "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc",
90 | "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db",
91 | "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b",
92 | "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea",
93 | "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6",
94 | "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920",
95 | "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749",
96 | "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7",
97 | "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd",
98 | "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99",
99 | "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242",
100 | "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee",
101 | "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129",
102 | "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2",
103 | "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51",
104 | "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee",
105 | "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8",
106 | "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b",
107 | "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613",
108 | "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742",
109 | "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe",
110 | "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3",
111 | "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5",
112 | "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631",
113 | "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7",
114 | "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15",
115 | "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c",
116 | "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea",
117 | "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417",
118 | "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250",
119 | "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88",
120 | "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca",
121 | "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa",
122 | "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99",
123 | "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149",
124 | "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41",
125 | "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574",
126 | "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0",
127 | "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f",
128 | "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d",
129 | "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654",
130 | "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3",
131 | "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19",
132 | "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90",
133 | "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578",
134 | "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9",
135 | "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1",
136 | "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51",
137 | "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719",
138 | "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236",
139 | "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a",
140 | "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c",
141 | "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade",
142 | "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944",
143 | "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc",
144 | "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6",
145 | "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6",
146 | "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27",
147 | "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6",
148 | "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2",
149 | "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12",
150 | "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf",
151 | "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114",
152 | "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7",
153 | "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf",
154 | "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d",
155 | "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b",
156 | "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed",
157 | "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03",
158 | "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4",
159 | "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67",
160 | "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365",
161 | "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a",
162 | "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748",
163 | "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b",
164 | "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079",
165 | "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"
166 | ],
167 | "markers": "python_full_version >= '3.7.0'",
168 | "version": "==3.4.0"
169 | },
170 | "idna": {
171 | "hashes": [
172 | "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
173 | "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
174 | ],
175 | "markers": "python_version >= '3.6'",
176 | "version": "==3.10"
177 | },
178 | "jmespath": {
179 | "hashes": [
180 | "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980",
181 | "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"
182 | ],
183 | "markers": "python_version >= '3.7'",
184 | "version": "==1.0.1"
185 | },
186 | "jsonschema": {
187 | "hashes": [
188 | "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4",
189 | "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"
190 | ],
191 | "markers": "python_version >= '3.8'",
192 | "version": "==4.23.0"
193 | },
194 | "jsonschema-specifications": {
195 | "hashes": [
196 | "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272",
197 | "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"
198 | ],
199 | "markers": "python_version >= '3.9'",
200 | "version": "==2024.10.1"
201 | },
202 | "python-dateutil": {
203 | "hashes": [
204 | "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
205 | "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
206 | ],
207 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
208 | "version": "==2.9.0.post0"
209 | },
210 | "referencing": {
211 | "hashes": [
212 | "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c",
213 | "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"
214 | ],
215 | "markers": "python_version >= '3.8'",
216 | "version": "==0.35.1"
217 | },
218 | "requests": {
219 | "extras": [
220 | "security"
221 | ],
222 | "hashes": [
223 | "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
224 | "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
225 | ],
226 | "index": "pypi",
227 | "markers": "python_version >= '3.8'",
228 | "version": "==2.32.3"
229 | },
230 | "rpds-py": {
231 | "hashes": [
232 | "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba",
233 | "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d",
234 | "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e",
235 | "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a",
236 | "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202",
237 | "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271",
238 | "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250",
239 | "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d",
240 | "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928",
241 | "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0",
242 | "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d",
243 | "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333",
244 | "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e",
245 | "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a",
246 | "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18",
247 | "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044",
248 | "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677",
249 | "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664",
250 | "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75",
251 | "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89",
252 | "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027",
253 | "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9",
254 | "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e",
255 | "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8",
256 | "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44",
257 | "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3",
258 | "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95",
259 | "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd",
260 | "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab",
261 | "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a",
262 | "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560",
263 | "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035",
264 | "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919",
265 | "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c",
266 | "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266",
267 | "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e",
268 | "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592",
269 | "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9",
270 | "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3",
271 | "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624",
272 | "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9",
273 | "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b",
274 | "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f",
275 | "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca",
276 | "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1",
277 | "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8",
278 | "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590",
279 | "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed",
280 | "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952",
281 | "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11",
282 | "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061",
283 | "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c",
284 | "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74",
285 | "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c",
286 | "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94",
287 | "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c",
288 | "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8",
289 | "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf",
290 | "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a",
291 | "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5",
292 | "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6",
293 | "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5",
294 | "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3",
295 | "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed",
296 | "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87",
297 | "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b",
298 | "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72",
299 | "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05",
300 | "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed",
301 | "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f",
302 | "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c",
303 | "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153",
304 | "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b",
305 | "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0",
306 | "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d",
307 | "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d",
308 | "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e",
309 | "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e",
310 | "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd",
311 | "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682",
312 | "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4",
313 | "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db",
314 | "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976",
315 | "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937",
316 | "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1",
317 | "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb",
318 | "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a",
319 | "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7",
320 | "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356",
321 | "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"
322 | ],
323 | "markers": "python_version >= '3.9'",
324 | "version": "==0.21.0"
325 | },
326 | "s3transfer": {
327 | "hashes": [
328 | "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d",
329 | "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c"
330 | ],
331 | "markers": "python_version >= '3.8'",
332 | "version": "==0.10.3"
333 | },
334 | "six": {
335 | "hashes": [
336 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
337 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
338 | ],
339 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
340 | "version": "==1.16.0"
341 | },
342 | "urllib3": {
343 | "hashes": [
344 | "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e",
345 | "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"
346 | ],
347 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
348 | "version": "==1.26.20"
349 | }
350 | },
351 | "develop": {
352 | "exceptiongroup": {
353 | "hashes": [
354 | "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b",
355 | "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"
356 | ],
357 | "markers": "python_version < '3.11'",
358 | "version": "==1.2.2"
359 | },
360 | "iniconfig": {
361 | "hashes": [
362 | "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
363 | "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
364 | ],
365 | "markers": "python_version >= '3.7'",
366 | "version": "==2.0.0"
367 | },
368 | "packaging": {
369 | "hashes": [
370 | "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759",
371 | "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"
372 | ],
373 | "markers": "python_version >= '3.8'",
374 | "version": "==24.2"
375 | },
376 | "pluggy": {
377 | "hashes": [
378 | "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1",
379 | "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"
380 | ],
381 | "markers": "python_version >= '3.8'",
382 | "version": "==1.5.0"
383 | },
384 | "pytest": {
385 | "hashes": [
386 | "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181",
387 | "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"
388 | ],
389 | "index": "pypi",
390 | "markers": "python_version >= '3.8'",
391 | "version": "==8.3.3"
392 | },
393 | "tomli": {
394 | "hashes": [
395 | "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38",
396 | "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"
397 | ],
398 | "markers": "python_version < '3.11'",
399 | "version": "==2.0.2"
400 | }
401 | }
402 | }
403 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cfn-saml-provider
2 | A CloudFormation custom resource provider for adding a SAML identity provider.
3 |
4 | ## Syntax
5 | To create a SAML provider using your AWS CloudFormation template, use a [Custom::SAMLProvider](docs/saml-provider.md) resource with reference
6 | to the metadata URL:
7 |
8 | ```yaml
9 | SAMLProvider:
10 | Type: Custom::SAMLProvider
11 | Properties:
12 | Name: auth0
13 | URL: https://auth0.com/mytenant/providerurl
14 | ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:cfn-saml-provider'
15 | ```
16 | or with the metadata itself:
17 |
18 | ```yaml
19 | SAMLProvider:
20 | Type: Custom::SAMLProvider
21 | Properties:
22 | Name: auth0
23 | Metadata: |
24 |
25 | ....
26 |
27 | ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:cfn-saml-provider'
28 | ```
29 |
30 | On completion, it will return the ARN of the SAML Provider.
31 |
32 | ### Deploy the provider
33 | To deploy the provider, type:
34 |
35 | ```sh
36 | aws cloudformation create-stack \
37 | --capabilities CAPABILITY_IAM \
38 | --stack-name cfn-saml-provider \
39 | --template-body file://cloudformation/cfn-saml-provider.json
40 |
41 | aws cloudformation wait stack-create-complete --stack-name cfn-saml-provider
42 | ```
43 |
44 | This CloudFormation template will use our pre-packaged provider from `463637877380.dkr.ecr.eu-central-1.amazonaws.com/xebia/cfn-saml-provider:2.0.0`.
45 |
46 | ## Demo
47 | To install the simple sample of the SAML provider, type:
48 |
49 | ```sh
50 | aws cloudformation create-stack --stack-name cfn-saml-provider-demo \
51 | --template-body file://cloudformation/demo-stack.json
52 | aws cloudformation wait stack-create-complete --stack-name cfn-saml-provider-demo
53 | ```
54 |
55 | to validate the result, type:
56 |
57 | ```sh
58 | aws iam list-saml-providers
59 | ```
60 |
61 |
--------------------------------------------------------------------------------
/cloudformation/cfn-resource-provider.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Description: Auth0 Custom CloudFormation Provider
3 | Parameters:
4 | DefaultSecurityGroup:
5 | Type: String
6 | Default: ''
7 | S3BucketPrefix:
8 | Type: String
9 | Default: ''
10 | CFNCustomProviderZipFileName:
11 | Type: String
12 | Default: lambdas/cfn-saml-provider-1.0.0.zip
13 | Resources:
14 | CFNCustomProvider:
15 | Type: AWS::Lambda::Function
16 | DependsOn:
17 | - LambdaRole
18 | Properties:
19 | Description: Custom SAML Provider CloudFormation Provider
20 | PackageType: Image
21 | Code:
22 | ImageUri: 463637877380.dkr.ecr.eu-central-1.amazonaws.com/xebia/cfn-saml-provider:2.0.0
23 | FunctionName: cfn-saml-provider
24 | MemorySize: 128
25 | Timeout: 10
26 | Role: !GetAtt 'LambdaRole.Arn'
27 | LambdaPolicy:
28 | Type: AWS::IAM::Policy
29 | DependsOn:
30 | - LambdaRole
31 | Properties:
32 | PolicyName: CFNCustomProviderPolicy
33 | PolicyDocument:
34 | Version: '2012-10-17'
35 | Statement:
36 | - Effect: Allow
37 | Action:
38 | - iam:CreateSAMLProvider
39 | - iam:UpdateSAMLProvider
40 | - iam:DeleteSAMLProvider
41 | Resource: '*'
42 | Roles:
43 | - !Ref 'LambdaRole'
44 |
45 | LambdaRole:
46 | Type: AWS::IAM::Role
47 | Properties:
48 | ManagedPolicyArns:
49 | - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
50 | AssumeRolePolicyDocument:
51 | Version: '2012-10-17'
52 | Statement:
53 | - Action:
54 | - sts:AssumeRole
55 | Effect: Allow
56 | Principal:
57 | Service:
58 | - lambda.amazonaws.com
59 |
--------------------------------------------------------------------------------
/cloudformation/cicd-pipeline.yaml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: '2010-09-09'
2 | Parameters:
3 | Name:
4 | Type: String
5 |
6 | Resources:
7 | Project:
8 | Type: AWS::CodeBuild::Project
9 | DependsOn:
10 | - LogGroup
11 | Properties:
12 | Name: !Ref Name
13 | Description: !Sub '${Name} builder'
14 | ServiceRole: !Ref 'ProjectRole'
15 | Artifacts:
16 | Type: NO_ARTIFACTS
17 | Environment:
18 | Type: LINUX_CONTAINER
19 | ComputeType: BUILD_GENERAL1_SMALL
20 | Image: aws/codebuild/standard:7.0
21 | PrivilegedMode: true
22 | Source:
23 | Type: GITHUB
24 | Location: !Sub 'https://github.com/binxio/${Name}.git'
25 | BuildSpec: .buildspec.yaml
26 | GitCloneDepth: 0
27 | Auth:
28 | Type: OAUTH
29 | Triggers:
30 | FilterGroups:
31 | - - Type: EVENT
32 | Pattern: PUSH
33 | - Type: HEAD_REF
34 | Pattern: "refs/tags/.*"
35 | Webhook: true
36 |
37 | ProjectRole:
38 | Type: AWS::IAM::Role
39 | Properties:
40 | AssumeRolePolicyDocument:
41 | Statement:
42 | - Effect: Allow
43 | Action: sts:AssumeRole
44 | Principal:
45 | Service: codebuild.amazonaws.com
46 | Condition: {}
47 | Path: /
48 | Policies:
49 | - PolicyName: LambdaBuilder
50 | PolicyDocument:
51 | Statement:
52 | - Effect: Allow
53 | Action:
54 | - ec2:DescribeRegions
55 | - ecr:GetAuthorizationToken
56 | Resource:
57 | - '*'
58 |
59 | - Effect: Allow
60 | Action:
61 | - ecr:GetDownloadUrlForLayer
62 | - ecr:BatchGetImage
63 | - ecr:BatchCheckLayerAvailability
64 | - ecr:PutImage
65 | - ecr:InitiateLayerUpload
66 | - ecr:UploadLayerPart
67 | - ecr:CompleteLayerUpload
68 | Resource:
69 | - !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/xebia/${Name}'
70 |
71 | - Sid: CloudWatchLogsPolicy
72 | Effect: Allow
73 | Action:
74 | - logs:CreateLogGroup
75 | - logs:CreateLogStream
76 | - logs:PutLogEvents
77 | Resource:
78 | - '*'
79 |
80 | Repository:
81 | Type: AWS::ECR::Repository
82 | Properties:
83 | RepositoryName: !Sub xebia/${Name}
84 | ImageTagMutability: IMMUTABLE
85 | RepositoryPolicyText:
86 | Version: '2012-10-17'
87 | Statement:
88 | - Sid: read only access
89 | Effect: Allow
90 | Principal:
91 | AWS:
92 | - '*'
93 | Action:
94 | - ecr:GetDownloadUrlForLayer
95 | - ecr:BatchGetImage
96 | - ecr:BatchCheckLayerAvailability
97 |
98 | - Sid: write to owner account
99 | Effect: Allow
100 | Principal:
101 | AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
102 | Action:
103 | - ecr:GetDownloadUrlForLayer
104 | - ecr:BatchGetImage
105 | - ecr:BatchCheckLayerAvailability
106 | - ecr:PutImage
107 | - ecr:InitiateLayerUpload
108 | - ecr:UploadLayerPart
109 | - ecr:CompleteLayerUpload
110 |
111 | LogGroup:
112 | Type: AWS::Logs::LogGroup
113 | Properties:
114 | LogGroupName: !Sub '/aws/codebuild/${Name}'
115 | RetentionInDays: 7
116 |
117 |
--------------------------------------------------------------------------------
/cloudformation/demo-stack.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | AWSTemplateFormatVersion: '2010-09-09'
3 | Description: Demo SAML Custom Provider
4 |
5 | Resources:
6 | SAMLProvider:
7 | Type: Custom::SAMLProvider
8 | Properties:
9 | ServiceToken: !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:cfn-saml-provider'
10 | Name: !Sub 'saml-provider-${AWS::StackName}'
11 | URL: 'https://mvanholsteijn.eu.auth0.com/samlp/metadata/ZALyvKmxYYlTmfHW4m4lzkia60S1YsSf'
12 |
13 |
14 | Outputs:
15 | SAMLProvider:
16 | Description: Arn of the SAML provider
17 | Value: !Ref 'SAMLProvider'
18 |
--------------------------------------------------------------------------------
/docs/saml-provider.md:
--------------------------------------------------------------------------------
1 | # Custom::SAMLProvider
2 | The `Custom::SAMLProvider` creates IAM SAM Provider.
3 |
4 | ## Syntax
5 | To declare this entity in your AWS CloudFormation template, use the following syntax:
6 |
7 | ```yaml
8 | Type : Custom::SAMLProvider
9 | Properties
10 | Name: String
11 | Metadata: String
12 | URL: url
13 | ServiceToken" : !Sub 'arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:cfn-saml-provider'
14 | ```
15 |
16 | It will create a SAML provider named `Name` using the `Metadata` literal or the content
17 | of the metadata `URL`.
18 |
19 | ## Properties
20 | You can specify the following properties:
21 |
22 | "Name" - of the SAML provider (required)
23 | "Metadata" - for the SAML Provider (required if URL is missing)
24 | "URL" - serving the metadata of the SAML Provider (required if Metadaga is missing)
25 | "ServiceToken" - pointing to the function implementing this (required)
26 |
27 | ## Return values
28 | The physical resource id is the ARN of the provider. There are no additional return attributes.
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | -i https://pypi.org/simple
2 | attrs==24.2.0; python_version >= '3.7'
3 | boto3==1.35.57; python_version >= '3.8'
4 | botocore==1.35.57; python_version >= '3.8'
5 | certifi==2024.8.30; python_version >= '3.6'
6 | cfn-resource-provider==1.2.1
7 | charset-normalizer==3.4.0; python_full_version >= '3.7.0'
8 | idna==3.10; python_version >= '3.6'
9 | jmespath==1.0.1; python_version >= '3.7'
10 | jsonschema==4.23.0; python_version >= '3.8'
11 | jsonschema-specifications==2024.10.1; python_version >= '3.9'
12 | python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
13 | referencing==0.35.1; python_version >= '3.8'
14 | requests[security]==2.32.3; python_version >= '3.8'
15 | rpds-py==0.21.0; python_version >= '3.9'
16 | s3transfer==0.10.3; python_version >= '3.8'
17 | six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
18 | urllib3==1.26.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
19 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binxio/cfn-saml-provider/eab10ca4afda6273d44fa309c391ba2cb78de1a5/src/__init__.py
--------------------------------------------------------------------------------
/src/saml_provider.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import boto3
4 | import requests
5 | import logging
6 | from botocore.exceptions import ClientError
7 | from cfn_resource_provider import ResourceProvider
8 |
9 | logging.basicConfig(level=os.getenv('LOG_LEVEL', 'INFO'))
10 |
11 | #
12 | # The request schema defining the Resource Properties
13 | #
14 | request_schema = {
15 | "type": "object",
16 | "oneOf": [{
17 | "properties": {
18 | "Name": {
19 | "type": "string",
20 | "description": "of the saml provider"
21 | },
22 | "Metadata": {
23 | "type": "string",
24 | "description": "of the saml provider"
25 | }
26 | },
27 | "required": ["Name", "Metadata"]
28 | }, {
29 | "properties": {
30 | "Name": {
31 | "type": "string",
32 | "description": "of the saml provider"
33 | },
34 | "URL": {
35 | "type": "string",
36 | "description": "pointing to the SAML Metadata Document"
37 | }
38 | },
39 | "required": ["Name", "URL"]
40 | }
41 | ]
42 | }
43 |
44 | log = logging.getLogger(name=__name__)
45 |
46 |
47 | class SAMLProvider(ResourceProvider, object):
48 | """
49 | Generic Cloudformation custom resource provider for Auth0 resources.
50 | """
51 |
52 | def __init__(self):
53 | super(SAMLProvider, self).__init__()
54 | self.request_schema = request_schema
55 | self.iam = boto3.client('iam')
56 |
57 | @property
58 | def custom_cfn_resource_name(self):
59 | return 'Custom::SAMLProvider'
60 |
61 | def get_metadata(self):
62 | metadata = self.get('Metadata', None)
63 | if metadata != None:
64 | return None, metadata
65 |
66 | try:
67 | response = requests.get(self.get('URL'))
68 | metadata = response.text
69 | if response.status_code == 200:
70 | return None, metadata
71 | except requests.exceptions.RequestException as e:
72 | return '{}'.format(e), None
73 |
74 | return 'url %s returned status code %d, %s'.format(self.get('URL'), response.status_code, response.text), None
75 |
76 | def create(self):
77 | err, metadata = self.get_metadata()
78 | if err:
79 | self.physical_resource_id = 'could-not-create'
80 | self.fail(err)
81 | return
82 |
83 | try:
84 | response = self.iam.create_saml_provider(Name=self.get('Name'), SAMLMetadataDocument=metadata)
85 | self.physical_resource_id = response['SAMLProviderArn']
86 | except ClientError as e:
87 | self.physical_resource_id = 'could-not-create'
88 | self.fail('{}'.format(e))
89 |
90 | def update(self):
91 | err, metadata = self.get_metadata()
92 | if err:
93 | self.fail(err)
94 | return
95 |
96 | try:
97 | response = self.iam.update_saml_provider(SAMLProviderArn=self.physical_resource_id, SAMLMetadataDocument=metadata)
98 | self.physical_resource_id = response['SAMLProviderArn']
99 | except ClientError as e:
100 | self.fail('{}'.format(e))
101 |
102 | def delete(self):
103 | if re.match(r'arn:aws[-a-z]*:iam::[0-9]*:saml-provider/.*$', self.physical_resource_id):
104 | response = self.iam.delete_saml_provider(SAMLProviderArn=self.physical_resource_id)
105 |
106 |
107 | provider = SAMLProvider()
108 |
109 |
110 | def handler(request, context):
111 | return provider.handle(request, context)
112 |
--------------------------------------------------------------------------------
/test-requirements.txt:
--------------------------------------------------------------------------------
1 | -i https://pypi.org/simple
2 | exceptiongroup==1.2.2; python_version < '3.11'
3 | iniconfig==2.0.0; python_version >= '3.7'
4 | packaging==24.2; python_version >= '3.8'
5 | pluggy==1.5.0; python_version >= '3.8'
6 | pytest==8.3.3; python_version >= '3.8'
7 | tomli==2.0.2; python_version < '3.11'
8 |
--------------------------------------------------------------------------------
/tests/test_saml_provider.py:
--------------------------------------------------------------------------------
1 | import json
2 | import uuid
3 |
4 | import boto3
5 | from botocore.exceptions import ClientError
6 |
7 | from saml_provider import handler
8 |
9 | metadata = '''
10 |
11 |
12 |
13 |
14 | MIICsjCCAZqgAwIBAgIJaqWEM+WWXxFJMA0GCSqGSIb3DQEBBQUAMAAwHhcNMTYwMTA0MTQzNjQ3WhcNMjkwOTEyMTQzNjQ3WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtPbIvSsliCGVS3g++KcXwzAATBtHT5YN1bUbisW7n16Ri0IzXLWCyeGdEtvzMr78aCQQ5IfRx1LGJc3x4JTFRJY5x5vpQrkUfmpVdly3sPmpD4oo4Q6qQy8WDOmskplHRevbkVj6QXFp571JpNyzq6+s22Kjp01btA17OhJEBrrFlIYZIDfH/zm4EEABL5UBSpXCJEjkxBkXbmPRbyyfGWxX4vDVNFar7J8PdOg4D7Wvhunug8mWeMIHurs+ZzwEc/CW1yqE2d/J9SG7jJD5dXLeKZeasjtPDDbHqoNc4qk9kfc2pWiPByYG/1wx3QXY/0of0ENUvqg3lLcTSmYetwIDAQABoy8wLTAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBSHkjmCxyhXEn4IZX8PltaoZtN5VTANBgkqhkiG9w0BAQUFAAOCAQEAD9bcO+YYz8bluf6kx3k3MsnsbD5Vat2Q49l46mdMI+gfqI9Q0iHOSVLwYoPOHqO2a3wuNVyz5kTsAJfPWaZpuiTZt+bkp4ZUQU5bsdtNQfZOvIhI8sIvI+bYKGgU3Q4DH2HslubH3Wrw07rFHIbsRlf2eOhPgH4E9cOONNgivwCyT5YlNAbk5ZdRvFvtigX5O3voMiVZfU+B+RPPMe7owYIboDEPy9murdoaF2GnoP3TMDtkAsdbd/DvsHtHyKOfSWTr6Ls7x3wWJ6+T7YO+qpyMm5guffOfX4kahyi2akOdIb806CbZ+WGnKRsSvx0u9Ps3PkNNxjVDcNJqmeXWgQ==
15 |
16 |
17 |
18 |
19 |
20 | urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
21 | urn:oasis:names:tc:SAML:2.0:nameid-format:persistent
22 | urn:oasis:names:tc:SAML:2.0:nameid-format:transient
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | '''
33 |
34 |
35 | def test_create_with_url():
36 | # create with url
37 | name = 'n%s' % uuid.uuid4()
38 | request = Request('Create', name, 'https://mvanholsteijn.eu.auth0.com/samlp/metadata/44dxGEx0lDr1cpPzBwr7jv9IyWcHGSiY')
39 | response = handler(request, {})
40 | assert response['Status'] == 'SUCCESS', response['Reason']
41 | assert 'PhysicalResourceId' in response
42 | physical_resource_id = response['PhysicalResourceId']
43 |
44 | # update
45 | request = Request('Update', name, metadata, physical_resource_id)
46 | response = handler(request, {})
47 | assert response['Status'] == 'SUCCESS', response['Reason']
48 | assert 'PhysicalResourceId' in response
49 |
50 | # delete
51 | request = Request('Delete', {}, physical_resource_id)
52 | response = handler(request, {})
53 | assert response['Status'] == 'SUCCESS', response['Reason']
54 |
55 |
56 | def test_create_with_bad_url():
57 | name = 'n%s' % uuid.uuid4()
58 | request = Request('Create', name, 'http://does-not-exist')
59 | response = handler(request, {})
60 | assert response['Status'] == 'FAILED', response['Reason']
61 | assert 'PhysicalResourceId' in response
62 | assert response['PhysicalResourceId'] == 'could-not-create'
63 |
64 |
65 | def test_create_with_bad_metadata():
66 | name = 'n%s' % uuid.uuid4()
67 | request = Request('Create', name, '' + (' ' * 1000) + '')
68 | response = handler(request, {})
69 | assert response['Status'] == 'FAILED', response['Reason']
70 | assert 'PhysicalResourceId' in response
71 | assert response['PhysicalResourceId'] == 'could-not-create'
72 |
73 |
74 | def test_create_with_metadata():
75 | # create with metadata
76 | name = 'n%s' % uuid.uuid4()
77 | request = Request('Create', name, metadata)
78 | response = handler(request, {})
79 | assert response['Status'] == 'SUCCESS', response['Reason']
80 | assert 'PhysicalResourceId' in response
81 | physical_resource_id = response['PhysicalResourceId']
82 |
83 | # update
84 | request = Request(
85 | 'Update', name, 'https://mvanholsteijn.eu.auth0.com/samlp/metadata/44dxGEx0lDr1cpPzBwr7jv9IyWcHGSiY', physical_resource_id)
86 | response = handler(request, {})
87 | assert response['Status'] == 'SUCCESS', response['Reason']
88 | assert 'PhysicalResourceId' in response
89 |
90 | # delete
91 | request = Request('Delete', {}, physical_resource_id)
92 | response = handler(request, {})
93 | assert response['Status'] == 'SUCCESS', response['Reason']
94 |
95 |
96 | def test_delete_incorrect_physical_resource_id():
97 | request = Request('Delete', {}, 'devapi-consumeradmin-ManageOwnConsumersPermission-Y38O818GY2PV')
98 | response = handler(request, {})
99 | assert response['Status'] == 'SUCCESS', response['Reason']
100 |
101 |
102 | class Request(dict):
103 |
104 | def __init__(self, request_type, name, metadata, physical_resource_id=None):
105 | request_id = 'request-%s' % uuid.uuid4()
106 | self.update({
107 | 'RequestType': request_type,
108 | 'ResponseURL': 'https://httpbin.org/put',
109 | 'StackId': 'arn:aws:cloudformation:us-west-2:EXAMPLE/stack-name/guid',
110 | 'RequestId': request_id,
111 | 'ResourceType': 'Custom::SAMLProvider',
112 | 'LogicalResourceId': 'MyCustom',
113 | 'ResourceProperties': {
114 | 'Name': name
115 | }})
116 |
117 | if metadata.startswith('http'):
118 | self['ResourceProperties']['URL'] = metadata
119 | else:
120 | self['ResourceProperties']['Metadata'] = metadata
121 |
122 | self['PhysicalResourceId'] = physical_resource_id if physical_resource_id is not None else 'initial-%s' % str(uuid.uuid4())
123 |
--------------------------------------------------------------------------------