pax_global_header00006660000000000000000000000064141111704600014505gustar00rootroot0000000000000052 comment=16a785a75ea18c4f90d5b1090d19fb2243c01298 golang-github-smallstep-cli-0.15.16+ds/000077500000000000000000000000001411117046000175575ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/.VERSION000066400000000000000000000000211411117046000206760ustar00rootroot00000000000000 (tag: v0.15.16) golang-github-smallstep-cli-0.15.16+ds/.gitattributes000066400000000000000000000000261411117046000224500ustar00rootroot00000000000000.VERSION export-subst golang-github-smallstep-cli-0.15.16+ds/.github/000077500000000000000000000000001411117046000211175ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/.github/ISSUE_TEMPLATE/000077500000000000000000000000001411117046000233025ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000010541411117046000257120ustar00rootroot00000000000000--- name: Bug Report about: Create a report to help us improve title: '' labels: bug, needs triage assignees: '' --- ### Subject of the issue Describe your issue here. ### Your environment * OS - * Version - ### Steps to reproduce Tell us how to reproduce this issue. Please provide a working demo, you can use [this template](https://plnkr.co/edit/XorWgI?p=preview) as a base. ### Expected behaviour Tell us what should happen ### Actual behaviour Tell us what happens instead ### Additional context Add any other context about the problem here. golang-github-smallstep-cli-0.15.16+ds/.github/ISSUE_TEMPLATE/documentation-request.md000066400000000000000000000006531411117046000301670ustar00rootroot00000000000000--- name: Documentation Request about: Request documentation for a feature title: '' labels: docs, needs triage assignees: '' --- golang-github-smallstep-cli-0.15.16+ds/.github/ISSUE_TEMPLATE/enhancement.md000066400000000000000000000003011411117046000261030ustar00rootroot00000000000000--- name: CLI Enhancement about: Suggest an enhancement to step cli title: '' labels: enhancement, needs triage assignees: '' --- ### What would you like to be added ### Why this is needed golang-github-smallstep-cli-0.15.16+ds/.github/PULL_REQUEST_TEMPLATE000066400000000000000000000001031411117046000243130ustar00rootroot00000000000000### Description Please describe your pull request. 💔Thank you! golang-github-smallstep-cli-0.15.16+ds/.github/workflows/000077500000000000000000000000001411117046000231545ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/.github/workflows/release.yml000066400000000000000000000204171411117046000253230ustar00rootroot00000000000000name: Create Release & Upload Assets on: push: # Sequence of patterns matched against refs/tags tags: - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 jobs: test: name: Lint, Test, Build runs-on: ubuntu-20.04 strategy: matrix: go: [ '1.15', '1.16' ] steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: 'latest' # Optional: working directory, useful for monorepos # working-directory: somedir # Optional: golangci-lint command line arguments. args: --timeout=30m # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true # Optional: if set to true then the action will use pre-installed Go. # skip-go-installation: true # Optional: if set to true then the action don't cache or restore ~/go/pkg. # skip-pkg-cache: true # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. # skip-build-cache: true - name: Test, Build id: lintTestBuild run: V=1 make ci create_release: name: Create Release needs: test runs-on: ubuntu-20.04 outputs: version: ${{ steps.extract-tag.outputs.VERSION }} vversion: ${{ steps.extract-tag.outputs.VVERSION }} is_prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} steps: - name: Extract Tag Names id: extract-tag run: | VVERSION=${GITHUB_REF#refs/tags/} VERSION=${GITHUB_REF#refs/tags/v} echo "::set-output name=VVERSION::${VVERSION}" echo "::set-output name=VERSION::${VERSION}" - name: Is Pre-release id: is_prerelease run: | set +e echo ${{ github.ref }} | grep "\-rc.*" OUT=$? if [ $OUT -eq 0 ]; then IS_PRERELEASE=true; else IS_PRERELEASE=false; fi echo "::set-output name=IS_PRERELEASE::${IS_PRERELEASE}" - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} draft: false prerelease: ${{ steps.is_prerelease.outputs.IS_PRERELEASE }} goreleaser: name: Upload Assets to Github w/ goreleaser runs-on: ubuntu-20.04 needs: create_release steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: Run GoReleaser uses: goreleaser/goreleaser-action@56f5b77f7fa4a8fe068bf22b732ec036cc9bc13f # v2.4.1 with: version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.PAT }} release_deb: name: Build & Release Debian package runs-on: ubuntu-20.04 needs: create_release steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: APT Install id: aptInstall run: sudo apt-get -y install build-essential debhelper fakeroot - name: Build Debian package id: build run: | PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin make debian - name: Upload Debian Package id: upload_deb run: | tag_name="${GITHUB_REF##*/}" hub release edit $(find ./.releases -type f -printf "-a %p ") -m "" "$tag_name" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} build_upload_docker: name: Build & Upload Docker Images runs-on: ubuntu-20.04 needs: test steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: Build id: build run: | PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin make docker-artifacts env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} # All jobs below this are for full releases (non release candidates e.g. *-rc.*) build_upload_aws_s3_binaries: name: Build & Upload AWS S3 Binaries runs-on: ubuntu-20.04 needs: create_release if: needs.create_release.outputs.is_prerelease == 'false' steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: Build id: build run: | PATH=$PATH:/usr/local/go/bin:/home/admin/go/bin make -j1 binary-linux binary-darwin binary-windows mkdir -p ./.releases cp ./output/binary/linux/bin/step ./.releases/step-linux-${{ needs.create_release.outputs.version }} cp ./output/binary/linux/bin/step ./.releases/step-linux-latest-integration cp ./output/binary/darwin/bin/step ./.releases/step-darwin-${{ needs.create_release.outputs.version }} cp ./output/binary/windows/bin/step ./.releases/step-windows-${{ needs.create_release.outputs.version }}.exe - name: Upload s3 id: upload-s3 uses: jakejarvis/s3-sync-action@v0.5.1 with: args: --acl public-read --follow-symlinks env: AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-1 SOURCE_DIR: ./.releases upload_windows_installer: name: Upload Windows Installer runs-on: ubuntu-20.04 needs: create_release if: needs.create_release.outputs.is_prerelease == 'false' steps: - name: Checkout uses: actions/checkout@v2 - name: Modify Installer id: modify run: sed -e 's~${CLI_VERSION}~'${{ needs.create_release.outputs.version }}'~g' ./upload/install-step.ps1.tpl > ./install-step.ps1 - name: Upload and Overwrite id: upload uses: prewk/s3-cp-action@v0.1.1 with: args: --acl public-read --follow-symlinks env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: us-east-1 DEST: s3://${{ secrets.AWS_s3_BUCKET }}/install-step.ps1 SOURCE: ./install-step.ps1 update_reference_docs: name: Update Reference Docs runs-on: ubuntu-20.04 needs: create_release if: needs.create_release.outputs.is_prerelease == 'false' steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: Build id: build run: V=1 make build - name: Checkout Docs uses: actions/checkout@master with: repository: smallstep/docs token: ${{ secrets.PAT }} path: './docs' - name: Update Reference id: update_refrence run: | ./bin/step help --markdown ./docs/step-cli/reference cd ./docs git config user.email "eng@smallstep.com" git config user.name "Github Action CI" git commit -a -m "step-cli ${{ needs.create_release.outputs.vversion }} reference update" - name: Push changes uses: ad-m/github-push-action@v0.6.0 with: github_token: ${{ secrets.PAT }} branch: 'master' directory: './docs' repository: 'smallstep/docs' golang-github-smallstep-cli-0.15.16+ds/.github/workflows/test.yml000066400000000000000000000033571411117046000246660ustar00rootroot00000000000000name: Lint, Test, Build on: push: tags-ignore: - 'v*' branches: - "**" pull_request: jobs: lintTestBuild: name: Lint, Test, Build runs-on: ubuntu-20.04 strategy: matrix: go: [ '1.15', '1.16' ] steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: 'latest' # Optional: working directory, useful for monorepos # working-directory: somedir # Optional: golangci-lint command line arguments. args: --timeout=30m # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true # Optional: if set to true then the action will use pre-installed Go. # skip-go-installation: true # Optional: if set to true then the action don't cache or restore ~/go/pkg. # skip-pkg-cache: true # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. # skip-build-cache: true - name: Test, Build id: lintTestBuild run: V=1 make ci - name: Codecov uses: codecov/codecov-action@v1.2.1 with: token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos file: ./coverage.out # optional name: codecov-umbrella # optional fail_ci_if_error: true # optional (default = false) golang-github-smallstep-cli-0.15.16+ds/.gitignore000066400000000000000000000004041411117046000215450ustar00rootroot00000000000000# Binaries for programs and plugins /bin/step *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Others *.swp .releases coverage.txt output vendor step golang-github-smallstep-cli-0.15.16+ds/.golangci.yml000066400000000000000000000034611411117046000221470ustar00rootroot00000000000000linters-settings: govet: check-shadowing: true settings: printf: funcs: - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf golint: min-confidence: 0 gocyclo: min-complexity: 10 maligned: suggest-new: true dupl: threshold: 100 goconst: min-len: 2 min-occurrences: 2 depguard: list-type: blacklist packages: # logging is allowed only by logutils.Log, logrus # is allowed to use only in logutils package - github.com/sirupsen/logrus misspell: locale: US lll: line-length: 140 goimports: local-prefixes: github.com/golangci/golangci-lint gocritic: enabled-tags: - performance - style - experimental disabled-checks: - wrapperFunc - dupImport # https://github.com/go-critic/go-critic/issues/845 linters: disable-all: true enable: - gofmt - golint - govet - misspell - ineffassign - deadcode - staticcheck - unused - structcheck - gosimple run: skip-dirs: - pkg issues: exclude: - declaration of "err" shadows declaration at line - should have a package comment, unless it's in another file for this package - func `CLICommand. - error strings should not be capitalized or end with punctuation or a newline # golangci.com configuration # https://github.com/golangci/golangci/wiki/Configuration service: golangci-lint-version: 1.19.x # use the fixed version to not introduce new linters unexpectedly prepare: - echo "here I can run custom commands, but no preparation needed for this repo" golang-github-smallstep-cli-0.15.16+ds/.goreleaser.yml000066400000000000000000000125141411117046000225130ustar00rootroot00000000000000# This is an example .goreleaser.yml file with some sane defaults. # Make sure to check the documentation at http://goreleaser.com project_name: step before: hooks: # You may remove this if you don't use go modules. - go mod download # - go generate ./... builds: - env: - CGO_ENABLED=0 goos: - linux - windows - darwin goarch: - amd64 - arm - arm64 - 386 - mips - mips64 goarm: - 7 gomips: - hardfloat - softfloat ignore: - goos: darwin goarch: arm - goos: darwin goarch: 386 - goos: darwin goarch: mips - goos: darwin goarch: mips64 - goos: windows goarch: arm - goos: windows goarch: arm64 - goos: windows goarch: 386 - goos: windows goarch: mips - goos: windows goarch: mips64 flags: - -trimpath main: ./cmd/step/main.go binary: bin/step ldflags: - -w -X main.Version={{.Version}} -X main.BuildTime={{.Date}} archives: - # Can be used to change the archive formats for specific GOOSs. # Most common use case is to archive as zip on Windows. # Default is empty. name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Version }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}{{ if .Mips }}_{{ .Mips }}{{ end }}" format_overrides: - goos: windows format: zip wrap_in_directory: "{{ .ProjectName }}_{{ .Version }}" files: - README.md - LICENSE - autocomplete/* source: enabled: true name_template: '{{ .ProjectName }}_{{ .Version }}' checksum: name_template: 'checksums.txt' snapshot: name_template: "{{ .Tag }}-next" release: # Repo in which the release will be created. # Default is extracted from the origin remote URL or empty if its private hosted. # Note: it can only be one: either github, gitlab or gitea github: owner: smallstep name: cli # IDs of the archives to use. # Defaults to all. #ids: # - foo # - bar # If set to true, will not auto-publish the release. # Default is false. draft: false # If set to auto, will mark the release as not ready for production # in case there is an indicator for this in the tag e.g. v1.0.0-rc1 # If set to true, will mark the release as not ready for production. # Default is false. prerelease: auto # You can change the name of the release. # Default is `{{.Tag}}` #name_template: "{{.ProjectName}}-v{{.Version}} {{.Env.USER}}" # You can disable this pipe in order to not upload any artifacts. # Defaults to false. #disable: true # You can add extra pre-existing files to the release. # The filename on the release will be the last part of the path (base). If # another file with the same name exists, the latest one found will be used. # Defaults to empty. #extra_files: # - glob: ./path/to/file.txt # - glob: ./glob/**/to/**/file/**/* # - glob: ./glob/foo/to/bar/file/foobar/override_from_previous scoop: # Template for the url which is determined by the given Token (github or gitlab) # Default for github is "https://github.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" # Default for gitlab is "https://gitlab.com///uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}" # Default for gitea is "https://gitea.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" url_template: "http://github.com/smallstep/cli/releases/download/{{ .Tag }}/{{ .ArtifactName }}" # Repository to push the app manifest to. bucket: owner: smallstep name: scoop-bucket # Git author used to commit to the repository. # Defaults are shown. commit_author: name: goreleaserbot email: goreleaser@smallstep.com # The project name and current git tag are used in the format string. commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}" # Your app's homepage. # Default is empty. homepage: "https://smallstep.com/" # Skip uploads for prerelease. skip_upload: auto # Your app's description. # Default is empty. description: "Crypto toolkit for working with X.509, OAuth, JWT, OATH OTP, etc." # Your app's license # Default is empty. license: "Apache-2.0" #dockers: # - dockerfile: docker/Dockerfile # goos: linux # goarch: amd64 # use_buildx: true # image_templates: # - "smallstep/step-cli:latest" # - "smallstep/step-cli:{{ .Tag }}" # build_flag_templates: # - "--platform=linux/amd64" # - dockerfile: docker/Dockerfile # goos: linux # goarch: 386 # use_buildx: true # image_templates: # - "smallstep/step-cli:latest" # - "smallstep/step-cli:{{ .Tag }}" # build_flag_templates: # - "--platform=linux/386" # - dockerfile: docker/Dockerfile # goos: linux # goarch: arm # goarm: 7 # use_buildx: true # image_templates: # - "smallstep/step-cli:latest" # - "smallstep/step-cli:{{ .Tag }}" # build_flag_templates: # - "--platform=linux/arm/v7" # - dockerfile: docker/Dockerfile # goos: linux # goarch: arm64 # use_buildx: true # image_templates: # - "smallstep/step-cli:latest" # - "smallstep/step-cli:{{ .Tag }}" # build_flag_templates: # - "--platform=linux/arm64/v8" golang-github-smallstep-cli-0.15.16+ds/.version.sh000077500000000000000000000003051411117046000216570ustar00rootroot00000000000000#!/usr/bin/env bash read -r firstline < .VERSION last_half="${firstline##*tag: }" if [[ ${last_half::1} == "v" ]]; then version_string="${last_half%%[,)]*}" fi echo "${version_string:-v0.0.0}" golang-github-smallstep-cli-0.15.16+ds/CHANGELOG.md000066400000000000000000000016011411117046000213660ustar00rootroot00000000000000# Changelog NOTE: Please look to the technical section of the [smallstep blog](https://smallstep.com/tags/technical/) for all release notes for step cli and certificates. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [0.0.2] ### Added - `--bundle` flag to cert/inspect for inpecting all the full chain or bundle given a path. Default behavior is unchanged; only inspect the first (leaf) certificate. - distribution.md with documentation on how to create releases. - travis build and upload artifacts to Github Releases on tagged pushes. - logging of invalid http requests to the oauth server ### Changed - default PEM format encryption alg AES128 -> AES256 ### Deprecated ### Removed ### Fixed ### Security ## [0.0.1] - 08.07.2018 ### Added - Initial version of `step` golang-github-smallstep-cli-0.15.16+ds/LICENSE000066400000000000000000000261271411117046000205740ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Smallstep Labs, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-smallstep-cli-0.15.16+ds/Makefile000066400000000000000000000102311411117046000212140ustar00rootroot00000000000000all: lint test build ci: test build .PHONY: all ci ################################################# # Determine the type of `push` and `version` ################################################# # If TRAVIS_TAG is set then we know this ref has been tagged. ifdef TRAVIS_TAG VERSION ?= $(TRAVIS_TAG) NOT_RC := $(shell echo $(VERSION) | grep -v -e -rc) ifeq ($(NOT_RC),) PUSHTYPE := release-candidate else PUSHTYPE := release endif # GITHUB Actions else ifdef GITHUB_REF VERSION ?= $(shell echo $(GITHUB_REF) | sed 's/^refs\/tags\///') NOT_RC := $(shell echo $(VERSION) | grep -v -e -rc) ifeq ($(NOT_RC),) PUSHTYPE := release-candidate else PUSHTYPE := release endif else VERSION ?= $(shell [ -d .git ] && git describe --tags --always --dirty="-dev") # If we are not in an active git dir then try reading the version from .VERSION. # .VERSION contains a slug populated by `git archive`. VERSION := $(or $(VERSION),$(shell ./.version.sh .VERSION)) ifeq ($(TRAVIS_BRANCH),master) PUSHTYPE := master else PUSHTYPE := branch endif endif VERSION := $(shell echo $(VERSION) | sed 's/^v//') DEB_VERSION := $(shell echo $(VERSION) | sed 's/-/~/') ifdef V $(info TRAVIS_TAG is $(TRAVIS_TAG)) $(info GITHUB_REF is $(GITHUB_REF)) $(info VERSION is $(VERSION)) $(info DEB_VERSION is $(DEB_VERSION)) $(info PUSHTYPE is $(PUSHTYPE)) endif include make/common.mk include make/docker.mk ######################################### # Debian ######################################### changelog: $Q echo "step-cli ($(DEB_VERSION)) unstable; urgency=medium" > debian/changelog $Q echo >> debian/changelog $Q echo " * See https://github.com/smallstep/cli/releases" >> debian/changelog $Q echo >> debian/changelog $Q echo " -- Smallstep Labs, Inc. $(shell date -uR)" >> debian/changelog debian: changelog $Q set -e; mkdir -p $(RELEASE); \ OUTPUT=../step-cli_*.deb; \ rm -f $$OUTPUT; \ dpkg-buildpackage -b -rfakeroot -us -uc && cp $$OUTPUT $(RELEASE)/ distclean: clean .PHONY: changelog debian distclean ################################################# # Build statically compiled step binary for various operating systems ################################################# BINARY_OUTPUT=$(OUTPUT_ROOT)binary/ RELEASE=./.releases define BUNDLE_MAKE # $(1) -- Go Operating System (e.g. linux, darwin, windows, etc.) # $(2) -- Go Architecture (e.g. amd64, arm, arm64, etc.) # $(3) -- Go ARM architectural family (e.g. 7, 8, etc.) # $(4) -- Parent directory for executables generated by 'make'. $(q) GOOS_OVERRIDE='GOOS=$(1) GOARCH=$(2) GOARM=$(3)' PREFIX=$(4) make $(4)bin/step endef binary-linux: $(call BUNDLE_MAKE,linux,amd64,,$(BINARY_OUTPUT)linux/) binary-linux-arm64: $(call BUNDLE_MAKE,linux,arm64,,$(BINARY_OUTPUT)linux.arm64/) binary-linux-armv7: $(call BUNDLE_MAKE,linux,arm,7,$(BINARY_OUTPUT)linux.armv7/) binary-linux-mips: $(call BUNDLE_MAKE,linux,mips,,$(BINARY_OUTPUT)linux.mips/) binary-darwin: $(call BUNDLE_MAKE,darwin,amd64,,$(BINARY_OUTPUT)darwin/) binary-windows: $(call BUNDLE_MAKE,windows,amd64,,$(BINARY_OUTPUT)windows/) define BUNDLE # $(1) -- Format output as .ZIP archive, rather than .tar.gzip (for older windows architecture) # $(2) -- Binary Output Dir Name # $(3) -- Step Platform Name # $(4) -- Step Binary Architecture # $(5) -- Step Binary Name (For Windows Comaptibility) $(q) ./make/bundle.sh $(1) "$(BINARY_OUTPUT)$(2)" "$(RELEASE)" "$(VERSION)" "$(3)" "$(4)" "$(5)" endef bundle-linux: binary-linux binary-linux-arm64 binary-linux-armv7 binary-linux-mips $(call BUNDLE,,linux,linux,amd64,step) $(call BUNDLE,,linux.arm64,linux,arm64,step) $(call BUNDLE,,linux.armv7,linux,armv7,step) $(call BUNDLE,,linux.mips,linux,mips,step) bundle-darwin: binary-darwin $(call BUNDLE,,darwin,darwin,amd64,step) bundle-windows: binary-windows $(call BUNDLE,,windows,windows,amd64,step.exe) $(call BUNDLE,--zip,windows,windows,amd64,step.exe) .PHONY: binary-linux binary-darwin binary-windows bundle-linux bundle-darwin bundle-windows ################################################# # Targets for creating step artifacts ################################################# docker-artifacts: docker-$(PUSHTYPE) .PHONY: docker-artifacts golang-github-smallstep-cli-0.15.16+ds/README.md000066400000000000000000000173151411117046000210450ustar00rootroot00000000000000# Step CLI [![GitHub release](https://img.shields.io/github/release/smallstep/cli.svg)](https://github.com/smallstep/cli/releases) [![CA Image](https://images.microbadger.com/badges/image/smallstep/step-cli.svg)](https://microbadger.com/images/smallstep/step-cli) [![Go Report Card](https://goreportcard.com/badge/github.com/smallstep/cli)](https://goreportcard.com/report/github.com/smallstep/cli) [![Build Status](https://travis-ci.com/smallstep/cli.svg?branch=master)](https://travis-ci.com/smallstep/cli) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CLA assistant](https://cla-assistant.io/readme/badge/smallstep/cli)](https://cla-assistant.io/smallstep/cli) [![GitHub stars](https://img.shields.io/github/stars/smallstep/cli.svg?style=social)](https://github.com/smallstep/cli/stargazers) [![Twitter followers](https://img.shields.io/twitter/follow/smallsteplabs.svg?label=Follow&style=social)](https://twitter.com/intent/follow?screen_name=smallsteplabs) `step` is a toolkit for working with your *public key infrastructure* (PKI). It's also the client counterpart to the [`step-ca`](https://github.com/smallstep/certificates) online Certificate Authority (CA). Here's a quick example, combining `step oauth` and `step crypto` to get and verify the signature of a Google OAuth OIDC token: ![Animated terminal showing step in practice](https://smallstep.com/images/blog/2018-08-07-unfurl.gif) **Questions? Ask us on [GitHub Discussions](https://github.com/smallstep/certificates/discussions) or [Gitter](https://gitter.im/smallstep/community).** [Website](https://smallstep.com) | [Documentation](https://smallstep.com/docs/step-cli) | [Installation](https://smallstep.com/docs/step-cli/installation) | [Getting Started](https://smallstep.com/docs/step-cli/basic-crypto-operations) | [Contributor's Guide](./docs/CONTRIBUTING.md) ## Features Step CLI's command groups illustrate some of its uses: - [`step certificate`](https://smallstep.com/docs/step-cli/reference/certificate/): Work with X.509 (TLS/HTTPS) certificates. - Create, revoke, validate, lint, and bundle X.509 certificates. - Install (and remove) X.509 certificates into your system's (and brower's) trust store. - Create key pairs (RSA, ECDSA, EdDSA) and certificate signing requests (CSRs) - [Sign CSRs](https://smallstep.com/docs/step-cli/reference/certificate/sign/) - Create [RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/)-compliant certificates that work for TLS and HTTPS - [Create](https://smallstep.com/docs/step-cli/reference/certificate/create/) CA certificates (root and intermediate signing certificates) - Create self-signed & CA-signed certificates - [Inspect](https://smallstep.com/docs/step-cli/reference/certificate/inspect/) and [lint](https://smallstep.com/docs/step-cli/reference/certificate/lint/) certificates on disk or in use by a remote server - [Install root certificates](https://smallstep.com/docs/step-cli/reference/certificate/install/) so your CA is trusted by default (issue development certificates **that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html)**) - [`step ca`](https://smallstep.com/docs/step-cli/reference/ca/): Set up your own CA, or make requests of any ACMEv2 ([RFC8555](https://tools.ietf.org/html/rfc8555)) CA, including [`step-ca`](https://github.com/smallstep/certificates). ACME is the protocol used by Let's Encrypt to automate the issuance of HTTPS certificates. - Initialize an X.509 and/or SSH CA in one command - [Authenticate and obtain a certificate](https://smallstep.com/docs/step-cli/reference/ca/certificate/) using any enrollment mechanism supported by [`step-ca`](https://github.com/smallstep/certificates) - Securely [distribute root certificates](https://smallstep.com/docs/step-cli/reference/ca/root/) and [bootstrap](https://smallstep.com/docs/step-cli/reference/ca/bootstrap/) PKI relying parties - [Renew](https://smallstep.com/docs/step-cli/reference/ca/renew/) and [revoke](https://smallstep.com/docs/step-cli/reference/ca/revoke/) certificates issued by [`step-ca`](https://github.com/smallstep/certificates) - [Submit CSRs](https://smallstep.com/docs/step-cli/reference/ca/sign/) to be signed by [`step-ca`](https://github.com/smallstep/certificates) - [`step crypto`](https://smallstep.com/docs/step-cli/reference/crypto/): A general-purpose crypto toolkit - Work with [JWTs](https://jwt.io) ([RFC7519](https://tools.ietf.org/html/rfc7519)) and [other JOSE constructs](https://datatracker.ietf.org/wg/jose/documents/) - [Sign](https://smallstep.com/docs/step-cli/reference/crypto/jwt/sign), [verify](https://smallstep.com/docs/step-cli/reference/crypto/jwt/verify), and [inspect](https://smallstep.com/docs/step-cli/reference/crypto/jwt/inspect) JSON Web Tokens (JWTs) - [Sign](https://smallstep.com/docs/step-cli/reference/crypto/jws/sign), [verify](https://smallstep.com/docs/step-cli/reference/crypto/jws/verify), and [inspect](https://smallstep.com/docs/step-cli/reference/crypto/jws/inspect/) arbitrary data using JSON Web Signature (JWS) - [Encrypt](https://smallstep.com/docs/step-cli/reference/crypto/jwe/encrypt/) and [decrypt](https://smallstep.com/docs/step-cli/reference/crypto/jwe/decrypt/) data and wrap private keys using JSON Web Encryption (JWE) - [Create JWKs](https://smallstep.com/docs/step-cli/reference/crypto/jwk/create/) and [manage key sets](https://smallstep.com/docs/step-cli/reference/crypto/jwk/keyset) for use with JWT, JWE, and JWS - [Generate and verify](https://smallstep.com/docs/step-cli/reference/crypto/otp/) TOTP tokens for multi-factor authentication (MFA) - Work with [NaCl](https://nacl.cr.yp.to/)'s high-speed tools for encryption and signing - [Apply key derivation functions](https://smallstep.com/docs/step-cli/reference/crypto/kdf/) (KDFs) and [verify passwords](https://smallstep.com/docs/step-cli/reference/crypto/kdf/compare/) using `scrypt`, `bcrypt`, and `argo2` - Generate and check [file hashes](https://smallstep.com/docs/step-cli/reference/crypto/hash/) - [`step oauth`](https://smallstep.com/docs/step-cli/reference/oauth/): Add an OAuth 2.0 single sign-on flow to any CLI application. - Supports OAuth authorization code, out-of-band (OOB), JWT bearer, and refresh token flows - Get OAuth access tokens and OIDC identity tokens at the command line from any provider. - Verify OIDC identity tokens (`step crypto jwt verify`) - [`step ssh`](https://smallstep.com/docs/step-cli/reference/ssh/): Create and manage SSH certificates (requires an online or offline [`step-ca`](https://github.com/smallstep/certificates) instance) - Generate SSH user and host key pairs and short-lived certificates - Add and remove certificates to the SSH agent - Inspect SSH certificates - Login and use [single sign-on SSH](https://smallstep.com/blog/diy-single-sign-on-for-ssh/) ## Installation See our installation docs [here](https://smallstep.com/docs/step-cli/installation). ## Community * Connect with `step` users on [GitHub Discussions](https://github.com/smallstep/certificates/discussions) or [Gitter](https://gitter.im/smallstep/community) * [Open an issue](https://github.com/smallstep/cli/issues/new/choose) and tell us what features you'd like to see * [Follow Smallstep on Twitter](https://twitter.com/smallsteplabs) ## Further Reading * [Full documentation for `step`](https://smallstep.com/docs/step-cli) * We have more examples of `step` and `step-ca` in action on [the Smallstep blog](https://smallstep.com/blog). * If you're new to PKI and X.509 certificates, or you want a refresher on the core concepts, you may enjoy [Everything PKI](https://smallstep.com/blog/everything-pki/). golang-github-smallstep-cli-0.15.16+ds/autocomplete/000077500000000000000000000000001411117046000222605ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/autocomplete/bash_autocomplete000077500000000000000000000005761411117046000257140ustar00rootroot00000000000000#! /bin/bash : ${PROG:=$(basename ${BASH_SOURCE})} _cli_bash_autocomplete() { local cur opts base COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) if [ -n "${opts}" ]; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) else _filedir fi return 0 } complete -F _cli_bash_autocomplete $PROG unset PROG golang-github-smallstep-cli-0.15.16+ds/autocomplete/zsh_autocomplete000066400000000000000000000004171411117046000255720ustar00rootroot00000000000000#compdef step function _step { local -a opts opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") if [[ "${opts}" != "" ]]; then _describe -t step-commands 'values' opts else _path_files fi } _step "$@" golang-github-smallstep-cli-0.15.16+ds/cmd/000077500000000000000000000000001411117046000203225ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/cmd/step/000077500000000000000000000000001411117046000212755ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/cmd/step/main.go000066400000000000000000000101471411117046000225530ustar00rootroot00000000000000package main import ( "fmt" "log" "math/rand" "net/http" "os" "reflect" "regexp" "strings" "time" "github.com/smallstep/certificates/ca" "github.com/smallstep/cli/command" "github.com/smallstep/cli/command/version" "github.com/smallstep/cli/config" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/usage" "github.com/urfave/cli" // Enabled commands _ "github.com/smallstep/cli/command/base64" _ "github.com/smallstep/cli/command/ca" _ "github.com/smallstep/cli/command/certificate" _ "github.com/smallstep/cli/command/crypto" _ "github.com/smallstep/cli/command/fileserver" _ "github.com/smallstep/cli/command/oauth" _ "github.com/smallstep/cli/command/path" _ "github.com/smallstep/cli/command/ssh" // Enabled cas interfaces. _ "github.com/smallstep/certificates/cas/cloudcas" _ "github.com/smallstep/certificates/cas/softcas" // Profiling and debugging _ "net/http/pprof" ) // Version is set by an LDFLAG at build time representing the git tag or commit // for the current release var Version = "N/A" // BuildTime is set by an LDFLAG at build time representing the timestamp at // the time of build var BuildTime = "N/A" func init() { config.Set("Smallstep CLI", Version, BuildTime) ca.UserAgent = config.Version() rand.Seed(time.Now().UnixNano()) } func main() { defer panicHandler() // Override global framework components cli.VersionPrinter = func(c *cli.Context) { version.Command(c) } cli.AppHelpTemplate = usage.AppHelpTemplate cli.SubcommandHelpTemplate = usage.SubcommandHelpTemplate cli.CommandHelpTemplate = usage.CommandHelpTemplate cli.HelpPrinter = usage.HelpPrinter cli.FlagNamePrefixer = usage.FlagNamePrefixer cli.FlagStringer = stringifyFlag // Configure cli app app := cli.NewApp() app.Name = "step" app.HelpName = "step" app.Usage = "plumbing for distributed systems" app.Version = config.Version() app.Commands = command.Retrieve() app.Flags = append(app.Flags, cli.HelpFlag) app.EnableBashCompletion = true app.Copyright = "(c) 2018-2020 Smallstep Labs, Inc." // Flag of custom configuration flag app.Flags = append(app.Flags, cli.StringFlag{ Name: "config", Usage: "path to the config file to use for CLI flags", }) // All non-successful output should be written to stderr app.Writer = os.Stdout app.ErrWriter = os.Stderr // Start the golang debug logger if environment variable is set. // See https://golang.org/pkg/net/http/pprof/ debugProfAddr := os.Getenv("STEP_PROF_ADDR") if debugProfAddr != "" { go func() { log.Println(http.ListenAndServe(debugProfAddr, nil)) }() } if err := app.Run(os.Args); err != nil { if fe, ok := err.(errs.FriendlyError); ok { if os.Getenv("STEPDEBUG") == "1" { fmt.Fprintf(os.Stderr, "%+v\n\n%s", err, fe.Message()) } else { fmt.Fprintln(os.Stderr, fe.Message()) fmt.Fprintln(os.Stderr, "Re-run with STEPDEBUG=1 for more info.") } } else { if os.Getenv("STEPDEBUG") == "1" { fmt.Fprintf(os.Stderr, "%+v\n", err) } else { fmt.Fprintln(os.Stderr, err) } } os.Exit(1) } } func panicHandler() { if r := recover(); r != nil { if os.Getenv("STEPDEBUG") == "1" { fmt.Fprintf(os.Stderr, "%s\n", config.Version()) fmt.Fprintf(os.Stderr, "Release Date: %s\n\n", config.ReleaseDate()) panic(r) } else { fmt.Fprintln(os.Stderr, "Something unexpected happened.") fmt.Fprintln(os.Stderr, "If you want to help us debug the problem, please run:") fmt.Fprintf(os.Stderr, "STEPDEBUG=1 %s\n", strings.Join(os.Args, " ")) fmt.Fprintln(os.Stderr, "and send the output to info@smallstep.com") os.Exit(2) } } } func flagValue(f cli.Flag) reflect.Value { fv := reflect.ValueOf(f) for fv.Kind() == reflect.Ptr { fv = reflect.Indirect(fv) } return fv } var placeholderString = regexp.MustCompile(`<.*?>`) func stringifyFlag(f cli.Flag) string { fv := flagValue(f) usage := fv.FieldByName("Usage").String() placeholder := placeholderString.FindString(usage) if placeholder == "" { switch f.(type) { case cli.BoolFlag, cli.BoolTFlag: default: placeholder = "" } } return cli.FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder) + "\t" + usage } golang-github-smallstep-cli-0.15.16+ds/command/000077500000000000000000000000001411117046000211755ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/command/README.md000066400000000000000000000073361411117046000224650ustar00rootroot00000000000000# How to add a new Command Before making any changes, please consult the [CLI style guide](https://github.com/urfave/cli)! ### Package Layout The [`urfave/cli`](https://github.com/urfave/cli) package that forms the basis of Step CLI supports N-levels of command hierarchy. Each level of the hierarchy should exist within its own package if possible. For example, `version` and `help` exist inside their own packages inside the top-level `command` package. Any package used by a command but does not contain explicit business logic directly related to the command should exist in the top-level of this repository. For example, the `github.com/smallstep/cli/flags` and `github.com/smallstep/cli/errs` package are used by many different commands and contain functionality for defining flags and creating/manipulating errors. ### Adding a Command Once you figured out where to add the command inside the package hierarchy you must register the command. This way the command *can* be made available if desired inside the `cmd/step/main.go`. An example of defining a command and registering it: ```golang package validate import ( "github.com/urfave/cli" "github.com/smallstep/cli/command" "github.com/smallstep/cli/flags" ) func init() { cmd := cli.Command{ Name: "validate", Usage: "Returns whether or not the provided token is valid", Flags: []cli.Flag{ flags.Token("The one-time token value to validate"), }, Action: validate, } command.Register(validate) } ``` Once this is done, you then must import the pkg inside `cmd/step/main.go` so the packages `init` method is run appropriately. This only needs to be done for top-level commands. ```golang package main import ( "github.com/urfave/cli" _ "github.com/smallstep/cli/validate" ) ``` This will ensure that the `smallstep/cli/validate` package is initialized and thus registered with the `smallstep/cli/command`. ### Usage, Flags, Errors, and Prompts There are three packages which contain functionality to make writing commands easier: - `github.com/smallstep/cli/usage` - `github.com/smallstep/cli/flags` - `github.com/smallstep/cli/prompts` - `github.com/smallstep/cli/errs` The usage package is used to extend the default documentation provided by `urfave/cli` by enabling us to document arguments, whether they are optional or required, and ensuring they're printed out as a part of the `step help` or `step -h` flow. If you need to add a different type of annotation to document an argument just add it to the `usage.Argument` struct! When you add a flag, look into the pre-existing ones inside the `flags` package. Could you use one of the pre-existing flags in order to reduce duplication? If not, make sure to add a flag so it could be used in future! The `errs` package contains functionality for defining and working with errors to ensure they are mutated properly into a `urfave/cli.ExitError` which ensures the process returns an appropriate exit code on termination. When you create an error, consider whether or not it's general and could be predefined inside the `errs` package. Errors that are specific to the command itself should exist only inside that commands respective package. The `prompts` package is a small wrapper around the various different types of prompts used by the commands. If you need a new prompt, consider adding a new function to this package to tailor the prompt for the step cli. This way other commands can adopt the step aesthetic as new functionality is introduced. ### Hiding a Command Sometimes it's desirable to prevent a command from showing up in the help menu because it's been deprecated *or* it's not ready for users to leverage. This can be achieved by setting the `Hidden` property on the `cli.Command` struct to `true`. golang-github-smallstep-cli-0.15.16+ds/command/base64/000077500000000000000000000000001411117046000222615ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/command/base64/base64.go000066400000000000000000000060401411117046000236740ustar00rootroot00000000000000package base64 import ( "bytes" "encoding/base64" "fmt" "os" "strings" "github.com/pkg/errors" "github.com/smallstep/cli/command" "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) func init() { cmd := cli.Command{ Name: "base64", Action: command.ActionFunc(base64Action), Usage: "encodes and decodes using base64 representation", UsageText: `**step base64** [**-d**|**--decode**] [**-r**|**--raw**] [**-u**|**--url**]`, Description: `**step base64** implements base64 encoding as specified by RFC 4648. ## Examples Encode to base64 using the standard encoding: ''' $ echo -n This is the string to encode | step base64 VGhpcyBpcyB0aGUgc3RyaW5nIHRvIGVuY29kZQ== $ step base64 This is the string to encode VGhpcyBpcyB0aGUgc3RyaW5nIHRvIGVuY29kZQ== ''' Decode a base64 encoded string: ''' $ echo VGhpcyBpcyB0aGUgc3RyaW5nIHRvIGVuY29kZQ== | step base64 -d This is the string to encode ''' Encode to base64 without padding: ''' $ echo -n This is the string to encode | step base64 -r VGhpcyBpcyB0aGUgc3RyaW5nIHRvIGVuY29kZQ $ step base64 -r This is the string to encode VGhpcyBpcyB0aGUgc3RyaW5nIHRvIGVuY29kZQ ''' Encode to base64 using the url encoding: ''' $ echo 'abc123$%^&*()_+-=~' | step base64 -u YWJjMTIzJCVeJiooKV8rLT1-Cg== ''' Decode an url encoded base64 string. The encoding type can be enforced using the '-u' or '-r' flags, but it will be autodetected if they are not passed: ''' $ echo YWJjMTIzJCVeJiooKV8rLT1-Cg== | step base64 -d abc123$%^&*()_+-=~ $ echo YWJjMTIzJCVeJiooKV8rLT1-Cg== | step base64 -d -u abc123$%^&*()_+-=~ '''`, Flags: []cli.Flag{ cli.BoolFlag{ Name: "d,decode", Usage: "decode base64 input", }, cli.BoolFlag{ Name: "r,raw", Usage: "use the unpadded base64 encoding", }, cli.BoolFlag{ Name: "u,url", Usage: "use the encoding format typically used in URLs and file names", }, }, } command.Register(cmd) } func base64Action(ctx *cli.Context) error { var err error var data []byte isDecode := ctx.Bool("decode") if ctx.NArg() > 0 { data = []byte(strings.Join(ctx.Args(), " ")) } else { var prompt string if isDecode { prompt = "Please enter text to decode" } else { prompt = "Please enter text to encode" } if data, err = utils.ReadInput(prompt); err != nil { return err } } enc := getEncoder(ctx, data) if isDecode { b, err := enc.DecodeString(string(data)) if err != nil { return errors.Wrap(err, "error decoding input") } os.Stdout.Write(b) } else { fmt.Println(enc.EncodeToString(data)) } return nil } func getEncoder(ctx *cli.Context, data []byte) *base64.Encoding { raw := ctx.Bool("raw") url := ctx.Bool("url") isDecode := ctx.Bool("decode") // Detect encoding if isDecode && !ctx.IsSet("raw") && !ctx.IsSet("url") { raw = !bytes.HasSuffix(bytes.TrimSpace(data), []byte("=")) url = bytes.Contains(data, []byte("-")) || bytes.Contains(data, []byte("_")) } if raw { if url { return base64.RawURLEncoding } return base64.RawStdEncoding } if url { return base64.URLEncoding } return base64.StdEncoding } golang-github-smallstep-cli-0.15.16+ds/command/ca/000077500000000000000000000000001411117046000215605ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/command/ca/bootstrap.go000066400000000000000000000112471411117046000241310ustar00rootroot00000000000000package ca import ( "encoding/json" "os" "path/filepath" "github.com/pkg/errors" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" "github.com/smallstep/cli/config" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils/cautils" "github.com/smallstep/truststore" "github.com/urfave/cli" ) func bootstrapCommand() cli.Command { return cli.Command{ Name: "bootstrap", Action: command.ActionFunc(bootstrapAction), Usage: "initialize the environment to use the CA commands", UsageText: `**step ca bootstrap** [**--ca-url**=] [**--fingerprint**=] [**--install**] [**--team**=name] [**--team-url**=url] [**--redirect-url**=]`, Description: `**step ca bootstrap** downloads the root certificate from the certificate authority and sets up the current environment to use it. Bootstrap will store the root certificate in <$STEPPATH/certs/root_ca.crt> and create a configuration file in <$STEPPATH/configs/defaults.json> with the CA url, the root certificate location and its fingerprint. After the bootstrap, ca commands do not need to specify the flags --ca-url, --root or --fingerprint if we want to use the same environment. ## EXAMPLES Bootstrap using the CA url and a fingerprint: ''' $ step ca bootstrap --ca-url https://ca.example.org \ --fingerprint d9d0978692f1c7cc791f5c343ce98771900721405e834cd27b9502cc719f5097 ''' Bootstrap and install the root certificate ''' $ step ca bootstrap --ca-url https://ca.example.org \ --fingerprint d9d0978692f1c7cc791f5c343ce98771900721405e834cd27b9502cc719f5097 \ --install ''' Bootstrap with a smallstep.com CA using a team ID: ''' $ step ca bootstrap --team superteam ''' To use team IDs in your own environment, you'll need an HTTP(S) server serving a JSON file: ''' {"url":"https://ca.example.org","fingerprint":"d9d0978692f1c7cc791f5c343ce98771900721405e834cd27b9502cc719f5097"} ''' Then, this command will look for the file at https://config.example.org/superteam: ''' $ step ca bootstrap --team superteam --team-url https://config.example.org/<> '''`, Flags: []cli.Flag{ flags.CaURL, fingerprintFlag, cli.BoolFlag{ Name: "install", Usage: "Install the root certificate into the system truststore.", }, flags.Team, flags.TeamURL, flags.RedirectURL, flags.Force, }, } } type bootstrapConfig struct { CA string `json:"ca-url"` Fingerprint string `json:"fingerprint"` Root string `json:"root"` Redirect string `json:"redirect-url"` } func bootstrapAction(ctx *cli.Context) error { caURL, err := flags.ParseCaURLIfExists(ctx) if err != nil { return err } fingerprint := ctx.String("fingerprint") team := ctx.String("team") rootFile := pki.GetRootCAPath() configFile := filepath.Join(config.StepPath(), "config", "defaults.json") redirectURL := ctx.String("redirect-url") switch { case team != "": return cautils.BootstrapTeam(ctx, team) case len(caURL) == 0: return errs.RequiredFlag(ctx, "ca-url") case len(fingerprint) == 0: return errs.RequiredFlag(ctx, "fingerprint") } tr := getInsecureTransport() client, err := ca.NewClient(caURL, ca.WithTransport(tr)) if err != nil { return err } // Root already validates the certificate resp, err := client.Root(fingerprint) if err != nil { return errors.Wrap(err, "error downloading root certificate") } if err = os.MkdirAll(filepath.Dir(rootFile), 0700); err != nil { return errs.FileError(err, rootFile) } if err = os.MkdirAll(filepath.Dir(configFile), 0700); err != nil { return errs.FileError(err, configFile) } // Serialize root _, err = pemutil.Serialize(resp.RootPEM.Certificate, pemutil.ToFile(rootFile, 0600)) if err != nil { return err } ui.Printf("The root certificate has been saved in %s.\n", rootFile) // make sure to store the url with https caURL, err = completeURL(caURL) if err != nil { return err } // Serialize defaults.json b, err := json.MarshalIndent(bootstrapConfig{ CA: caURL, Fingerprint: fingerprint, Root: pki.GetRootCAPath(), Redirect: redirectURL, }, "", " ") if err != nil { return errors.Wrap(err, "error marshaling defaults.json") } if err := utils.WriteFile(configFile, b, 0644); err != nil { return err } ui.Printf("Your configuration has been saved in %s.\n", configFile) if ctx.Bool("install") { ui.Printf("Installing the root certificate in the system truststore... ") if err := truststore.InstallFile(rootFile); err != nil { ui.Println() return err } ui.Println("done.") } return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/ca.go000066400000000000000000000134521411117046000224770ustar00rootroot00000000000000package ca import ( "net/url" "strings" "github.com/pkg/errors" "github.com/smallstep/cli/command" "github.com/smallstep/cli/command/ca/provisioner" "github.com/urfave/cli" ) // init creates and registers the ca command func init() { cmd := cli.Command{ Name: "ca", Usage: "initialize and manage a certificate authority", UsageText: "step ca [arguments] [global-flags] [subcommand-flags]", Description: `**step ca** command group provides facilities to initialize a certificate authority, retrieve the root of trust, sign and renew certificates, and create and manage provisioners. ## EXAMPLES Create the configuration for a new certificate authority: ''' $ step ca init ''' Configure the ca-url and root in the environment: ''' $ step ca bootstrap \ --ca-url https://ca.smallstep.com \ --fingerprint 0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3 $ cat $STEPPATH/config/defaults.json { "ca-url": "https://ca.smallstep.com", "fingerprint": "0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3", "root": "/home/user/.step/certs/root_ca.crt" } ''' Download the root_ca.crt: ''' $ step ca root root_ca.crt \ --ca-url https://ca.smallstep.com \ --fingerprint 0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3 ''' Get the Health status of the CA: ''' $ step ca health --ca-url https://ca.smallstep.com --root /home/user/.step/certs/root_ca.crt ''' Create a new certificate using a token: ''' $ TOKEN=$(step ca token internal.example.com) $ step ca certificate internal.example.com internal.crt internal.key \ --token $TOKEN --ca-url https://ca.smallstep.com --root root_ca.crt ''' Renew a certificate (certificate must still be valid): ''' $ step ca renew internal.crt internal.key \ --ca-url https://ca.smallstep.com --root root_ca.crt '''`, Subcommands: cli.Commands{ healthCommand(), initCommand(), bootstrapCommand(), tokenCommand(), certificateCommand(), renewCertificateCommand(), revokeCertificateCommand(), provisioner.Command(), signCertificateCommand(), rootComand(), rootsCommand(), federationCommand(), }, } command.Register(cmd) } // common flags used in several commands var ( acmeFlag = cli.StringFlag{ Name: "acme", Usage: `ACME directory to be used for requesting certificates via the ACME protocol. Use this flag to define an ACME server other than the Step CA. If this flag is absent and an ACME provisioner has been selected then the '--ca-url' flag must be defined.`, } acmeContactFlag = cli.StringSliceFlag{ Name: "contact", Usage: `The used for contact as part of the ACME protocol. These contacts may be used to warn of certificate expiration or other certificate lifetime events. Use the '--contact' flag multiple times to configure multiple contacts.`, } acmeHTTPListenFlag = cli.StringFlag{ Name: "http-listen", Usage: `Use a non-standard http
, behind a reverse proxy or load balancer, for serving ACME challenges. The default address is :80, which requires super user (sudo) privileges. This flag must be used in conjunction with the '--standalone' flag.`, Value: ":80", } /* TODO: Not implemented yet. acmeHTTPSListenFlag = cli.StringFlag{ Name: "https-listen", Usage: `Use a non-standard https address, behind a reverse proxy or load balancer, for serving ACME challenges. The default address is :443, which requires super user (sudo) privileges. This flag must be used in conjunction with the '--standalone' flag.`, Value: ":443", } */ acmeStandaloneFlag = cli.BoolFlag{ Name: "standalone", Usage: `Get a certificate using the ACME protocol and standalone mode for validation. Standalone is a mode in which the step process will run a server that will will respond to ACME challenge validation requests. Standalone is the default mode for serving challenge validation requests.`, } acmeWebrootFlag = cli.StringFlag{ Name: "webroot", Usage: `Specify a to use as a 'web root' for validation in the ACME protocol. Webroot is a mode in which the step process will write a challenge file to a location being served by an existing fileserver in order to respond to ACME challenge validation requests.`, } consoleFlag = cli.BoolFlag{ Name: "console", Usage: "Complete the flow while remaining inside the terminal", } fingerprintFlag = cli.StringFlag{ Name: "fingerprint", Usage: "The of the targeted root certificate.", } provisionerKidFlag = cli.StringFlag{ Name: "kid", Usage: "The provisioner to use.", } sshHostFlag = cli.BoolFlag{ Name: "host", Usage: `Create a host certificate instead of a user certificate.`, } ) // completeURL parses and validates the given URL. It supports general // URLs like https://ca.smallstep.com[:port][/path], and incomplete URLs like // ca.smallstep.com[:port][/path]. func completeURL(rawurl string) (string, error) { u, err := url.Parse(rawurl) if err != nil { return "", errors.Wrapf(err, "error parsing url '%s'", rawurl) } // URLs are generally parsed as: // [scheme:][//[userinfo@]host][/]path[?query][#fragment] // But URLs that do not start with a slash after the scheme are interpreted as // scheme:opaque[?query][#fragment] if u.Opaque == "" { if u.Scheme == "" { u.Scheme = "https" } if u.Host == "" { // rawurl looks like ca.smallstep.com or ca.smallstep.com/1.0/sign if u.Path != "" { parts := strings.SplitN(u.Path, "/", 2) u.Host = parts[0] if len(parts) == 2 { u.Path = parts[1] } else { u.Path = "" } return completeURL(u.String()) } return "", errors.Errorf("error parsing url '%s'", rawurl) } return u.String(), nil } // scheme:opaque[?query][#fragment] // rawurl looks like ca.smallstep.com:443 or ca.smallstep.com:443/1.0/sign return completeURL("https://" + rawurl) } golang-github-smallstep-cli-0.15.16+ds/command/ca/certificate.go000066400000000000000000000206541411117046000244000ustar00rootroot00000000000000package ca import ( "strings" "github.com/pkg/errors" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/token" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils/cautils" "github.com/urfave/cli" ) func certificateCommand() cli.Command { return cli.Command{ Name: "certificate", Action: command.ActionFunc(certificateAction), Usage: "generate a new private key and certificate signed by the root certificate", UsageText: `**step ca certificate** [**--token**=] [**--issuer**=] [**--ca-url**=] [**--root**=] [**--not-before**=] [**--not-after**=] [**--san**=] [**--set**=] [**--set-file**=] [**--acme**=] [**--standalone**] [**--webroot**=] [**--contact**=] [**--http-listen**=
] [**--bundle**] [**--kty**=] [**--curve**=] [**--size**=] [**--console**] [**--x5c-cert**=] [**--x5c-key**=] [**--k8ssa-token-path**=`, Description: `**step ca certificate** command generates a new certificate pair ## POSITIONAL ARGUMENTS : The Common Name, DNS Name, or IP address that will be set as the Subject Common Name for the certificate. If no Subject Alternative Names (SANs) are configured (via the --san flag) then the will be set as the only SAN. : File to write the certificate (PEM format) : File to write the private key (PEM format) ## EXAMPLES Request a new certificate for a given domain. There are no additional SANs configured, therefore (by default) the will be used as the only SAN extension: DNS Name internal.example.com: ''' $ TOKEN=$(step ca token internal.example.com) $ step ca certificate --token $TOKEN internal.example.com internal.crt internal.key ''' Request a new certificate with multiple Subject Alternative Names. The Subject Common Name of the certificate will be 'foobar'. However, because additional SANs are configured using the --san flag and 'foobar' is not one of these, 'foobar' will not be in the SAN extensions of the certificate. The certificate will have 2 IP Address extensions (1.1.1.1, 10.2.3.4) and 1 DNS Name extension (hello.example.com): ''' $ step ca certificate --san 1.1.1.1 --san hello.example.com --san 10.2.3.4 foobar internal.crt internal.key ''' Request a new certificate with a 1h validity: ''' $ TOKEN=$(step ca token internal.example.com) $ step ca certificate --token $TOKEN --not-after=1h internal.example.com internal.crt internal.key ''' Request a new certificate using the offline mode, requires the configuration files, certificates, and keys created with **step ca init**: ''' $ step ca certificate --offline internal.example.com internal.crt internal.key ''' Request a new certificate using an OIDC provisioner: ''' $ step ca certificate --token $(step oauth --oidc --bare) joe@example.com joe.crt joe.key ''' Request a new certificate using an OIDC provisioner while remaining in the console: ''' $ step ca certificate joe@example.com joe.crt joe.key --issuer Google --console ''' Request a new certificate with an RSA public key (default is ECDSA256): ''' $ step ca certificate foo.internal foo.crt foo.key --kty RSA --size 4096 ''' Request a new certificate with an X5C provisioner: ''' $ step ca certificate foo.internal foo.crt foo.key --x5c-cert x5c.cert --x5c-key x5c.key ''' **Certificate Templates** - With a provisioner configured with a custom template we can use the **--set** flag to pass user variables: ''' $ step ca certificate foo.internal foo.crt foo.key --set emailAddresses=root@internal.com $ step ca certificate foo.internal foo.crt foo.key --set emailAddresses='["foo@internal.com","root@internal.com"]' ''' Or you can pass them from a file using **--set-file**: ''' $ cat path/to/data.json { "emailAddresses": ["foo@internal.com","root@internal.com"] } $ step ca certificate foo.internal foo.crt foo.key --set-file path/to/data.json ''' **step CA ACME** - In order to use the step CA ACME protocol you must add a ACME provisioner to the step CA config. See **step ca provisioner add -h**. Request a new certificate using the step CA ACME server and a standalone server to serve the challenges locally (standalone mode is the default): ''' $ step ca certificate foobar foo.crt foo.key --provisioner my-acme-provisioner --san foo.internal --san bar.internal ''' Request a new certificate using the step CA ACME server and an existing server along with webroot mode to serve the challenges locally: ''' $ step ca certificate foobar foo.crt foo.key --provisioner my-acme-provisioner --webroot "./acme-www" \ --san foo.internal --san bar.internal ''' Request a new certificate using the ACME protocol not served via the step CA (e.g. letsencrypt). NOTE: Let's Encrypt requires that the Subject Common Name of a requested certificate be validated as an Identifier in the ACME order along with any other SANS. Therefore, the Common Name must be a valid DNS Name. The step CA does not impose this requirement. ''' $ step ca certificate foo.internal foo.crt foo.key \ --acme https://acme-staging-v02.api.letsencrypt.org/directory --san bar.internal '''`, Flags: []cli.Flag{ cli.StringSliceFlag{ Name: "san", Usage: `Add Subject Alternative Name(s) (SANs) that should be authorized. Use the '--san' flag multiple times to configure multiple SANs. The '--san' flag and the '--token' flag are mutually exclusive.`, }, flags.TemplateSet, flags.TemplateSetFile, flags.CaConfig, flags.CaURL, flags.Root, flags.Token, flags.Provisioner, flags.ProvisionerPasswordFile, flags.KTY, flags.Curve, flags.Size, flags.NotAfter, flags.NotBefore, flags.Force, flags.Offline, consoleFlag, flags.X5cCert, flags.X5cKey, acmeFlag, acmeStandaloneFlag, acmeWebrootFlag, acmeContactFlag, acmeHTTPListenFlag, flags.K8sSATokenPathFlag, }, } } func certificateAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 3); err != nil { return err } args := ctx.Args() subject := args.Get(0) crtFile, keyFile := args.Get(1), args.Get(2) tok := ctx.String("token") offline := ctx.Bool("offline") sans := ctx.StringSlice("san") // offline and token are incompatible because the token is generated before // the start of the offline CA. if offline && len(tok) != 0 { return errs.IncompatibleFlagWithFlag(ctx, "offline", "token") } // certificate flow unifies online and offline flows on a single api flow, err := cautils.NewCertificateFlow(ctx) if err != nil { return err } if len(tok) == 0 { // Use the ACME protocol with a different certificate authority. if ctx.IsSet("acme") { return cautils.ACMECreateCertFlow(ctx, "") } if tok, err = flow.GenerateToken(ctx, subject, sans); err != nil { switch k := err.(type) { // Use the ACME flow with the step certificate authority. case *cautils.ErrACMEToken: return cautils.ACMECreateCertFlow(ctx, k.Name) default: return err } } } req, pk, err := flow.CreateSignRequest(ctx, tok, subject, sans) if err != nil { return err } jwt, err := token.ParseInsecure(tok) if err != nil { return err } switch jwt.Payload.Type() { case token.JWK: // Validate that subject matches the CSR common name. if ctx.String("token") != "" && len(sans) > 0 { return errs.MutuallyExclusiveFlags(ctx, "token", "san") } if !strings.EqualFold(subject, req.CsrPEM.Subject.CommonName) { return errors.Errorf("token subject '%s' and argument '%s' do not match", req.CsrPEM.Subject.CommonName, subject) } case token.OIDC: // Validate that the subject matches an email SAN if len(req.CsrPEM.EmailAddresses) == 0 { return errors.New("unexpected token: payload does not contain an email claim") } if email := req.CsrPEM.EmailAddresses[0]; email != subject { return errors.Errorf("token email '%s' and argument '%s' do not match", email, subject) } case token.AWS, token.GCP, token.Azure, token.K8sSA: // Common name will be validated on the server side, it depends on // server configuration. default: return errors.New("token is not supported") } if err = flow.Sign(ctx, tok, req.CsrPEM, crtFile); err != nil { return err } _, err = pemutil.Serialize(pk, pemutil.ToFile(keyFile, 0600)) if err != nil { return err } ui.PrintSelected("Certificate", crtFile) ui.PrintSelected("Private Key", keyFile) return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/federation.go000066400000000000000000000077361411117046000242440ustar00rootroot00000000000000package ca import ( "encoding/pem" "fmt" "os" "github.com/pkg/errors" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) type flowType int const ( rootsFlow flowType = iota federationFlow ) func rootsCommand() cli.Command { return cli.Command{ Name: "roots", Action: command.ActionFunc(rootsAction), Usage: "download all the root certificates", UsageText: `**step ca roots** [] [**--ca-url**=] [**--root**=]`, Description: `**step ca roots** downloads a certificate bundle with all the root certificates. ## POSITIONAL ARGUMENTS : File to write all the root certificates (PEM format) ## EXAMPLES Download the roots with flags set by : ''' $ step ca roots roots.pem ''' Download the roots with custom flags: ''' $ step ca roots roots.pem \ --ca-url https://ca.example.com \ --root /path/to/root_ca.crt ''' Print the roots using flags set by : ''' $ step ca roots '''`, Flags: []cli.Flag{ flags.CaURL, flags.Force, flags.Root, }, } } func federationCommand() cli.Command { return cli.Command{ Name: "federation", Action: command.ActionFunc(federationAction), Usage: "download all the federated certificates", UsageText: `**step ca federation** [] [**--ca-url**=] [**--root**=]`, Description: `**step ca federation** downloads a certificate bundle with all the root certificates in the federation. ## POSITIONAL ARGUMENTS : File to write federation certificates (PEM format) ## EXAMPLES Download the federated roots with flags set by : ''' $ step ca federation federation.pem ''' Download the federated roots with custom flags: ''' $ step ca federation federation.pem \ --ca-url https://ca.example.com \ --root /path/to/root_ca.crt ''' Print the federated roots using flags set by : ''' $ step ca federation '''`, Flags: []cli.Flag{ flags.CaURL, flags.Force, flags.Root, }, } } func rootsAction(ctx *cli.Context) error { return rootsAndFederationFlow(ctx, rootsFlow) } func federationAction(ctx *cli.Context) error { return rootsAndFederationFlow(ctx, federationFlow) } func rootsAndFederationFlow(ctx *cli.Context, typ flowType) error { if err := errs.MinMaxNumberOfArguments(ctx, 0, 1); err != nil { return err } caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } root := ctx.String("root") if len(root) == 0 { root = pki.GetRootCAPath() if _, err := os.Stat(root); err != nil { return errs.RequiredFlag(ctx, "root") } } client, err := ca.NewClient(caURL, ca.WithRootFile(root)) if err != nil { return err } var certs []api.Certificate switch typ { case rootsFlow: roots, err := client.Roots() if err != nil { return err } certs = roots.Certificates case federationFlow: federation, err := client.Federation() if err != nil { return err } certs = federation.Certificates default: return errors.New("unknown flow type: this should not happen") } var data []byte for _, cert := range certs { block, err := pemutil.Serialize(cert.Certificate) if err != nil { return err } data = append(data, pem.EncodeToMemory(block)...) } if outFile := ctx.Args().Get(0); outFile != "" { if err := utils.WriteFile(outFile, data, 0600); err != nil { return err } switch typ { case rootsFlow: ui.Printf("The root certificate bundle has been saved in %s.\n", outFile) case federationFlow: ui.Printf("The federation certificate bundle has been saved in %s.\n", outFile) default: return errors.New("unknown flow type: this should not happen") } } else { fmt.Print(string(data)) } return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/health.go000066400000000000000000000035361411117046000233630ustar00rootroot00000000000000package ca import ( "fmt" "os" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/urfave/cli" ) func healthCommand() cli.Command { return cli.Command{ Name: "health", Action: healthAction, Usage: "get the status of the CA", UsageText: `**step ca health** [**--ca-url**=] [**--root**=]`, Description: `**step ca health** makes an API request to the /health endpoint of the Step CA to check if it is running. If the CA is healthy, the response will be 'ok'. ## EXAMPLES Using the required flags: ''' $ step ca health --ca-url https://ca.smallstep.com:8080 --root path/to/root_ca.crt ok ''' With the required flags preconfigured: **--ca-url** is set using environment variables (as STEP_CA_URL) or the default configuration file in <$STEPPATH/config/defaults.json>. **--root** is set using environment variables (as STEP_ROOT), the default configuration file in <$STEPPATH/config/defaults.json> or the default root certificate located in <$STEPPATH/certs/root_ca.crt> ''' $ step ca health ok '''`, Flags: []cli.Flag{ flags.CaURL, flags.Root, }, } } func healthAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 0); err != nil { return err } caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } root := ctx.String("root") // Prepare client for bootstrap or provisioning tokens if len(root) == 0 { root = pki.GetRootCAPath() if _, err := os.Stat(root); err != nil { return errs.RequiredFlag(ctx, "root") } } var options []ca.ClientOption options = append(options, ca.WithRootFile(root)) client, err := ca.NewClient(caURL, options...) if err != nil { return err } r, err := client.Health() if err != nil { return err } fmt.Printf("%v\n", r.Status) return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/init.go000066400000000000000000000247431411117046000230640ustar00rootroot00000000000000package ca import ( "crypto/rand" "crypto/x509" "fmt" "io" "strings" "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) func initCommand() cli.Command { return cli.Command{ Name: "init", Action: cli.ActionFunc(initAction), Usage: "initialize the CA PKI", UsageText: `**step ca init** [**--root**=] [**--key**=] [**--pki**] [**--ssh**] [**--name**=] [**--dns**=] [**--address**=
] [**--provisioner**=] [**--provisioner-password-file**=] [**--password-file**=] [**--with-ca-url**=] [**--no-db**]`, Description: `**step ca init** command initializes a public key infrastructure (PKI) to be used by the Certificate Authority.`, Flags: []cli.Flag{ cli.StringFlag{ Name: "root", Usage: "The path of an existing PEM to be used as the root certificate authority.", EnvVar: command.IgnoreEnvVar, }, cli.StringFlag{ Name: "key", Usage: "The path of an existing key of the root certificate authority.", EnvVar: command.IgnoreEnvVar, }, cli.BoolFlag{ Name: "pki", Usage: "Generate only the PKI without the CA configuration.", }, cli.BoolFlag{ Name: "ssh", Usage: `Create keys to sign SSH certificates.`, }, cli.StringFlag{ Name: "name", Usage: "The of the new PKI.", }, cli.StringFlag{ Name: "dns", Usage: "The comma separated DNS or IP addresses of the new CA.", }, cli.StringFlag{ Name: "address", Usage: "The
that the new CA will listen at.", }, cli.StringFlag{ Name: "provisioner", Usage: "The of the first provisioner.", }, cli.StringFlag{ Name: "password-file", Usage: `The path to the containing the password to encrypt the keys.`, }, cli.StringFlag{ Name: "provisioner-password-file", Usage: `The path to the containing the password to encrypt the provisioner key.`, }, cli.StringFlag{ Name: "with-ca-url", Usage: ` of the Step Certificate Authority to write in defaults.json`, }, cli.StringFlag{ Name: "ra", Usage: `The registration authority to use. Currently only "CloudCAS" is supported.`, }, cli.StringFlag{ Name: "issuer", Usage: `The registration authority issuer to use. : If CloudCAS is used, this flag should be the resource name of the intermediate certificate to use. This has the format 'projects/\\*/locations/\\*/certificateAuthorities/\\*'.`, }, cli.StringFlag{ Name: "credentials-file", Usage: `The registration authority credentials to use. : If CloudCAS is used, this flag should be the path to a service account key. It can also be set using the 'GOOGLE_APPLICATION_CREDENTIALS=path' environment variable or the default service account in an instance in Google Cloud.`, }, cli.BoolFlag{ Name: "no-db", Usage: `Generate a CA configuration without the DB stanza. No persistence layer.`, }, }, } } func initAction(ctx *cli.Context) (err error) { if err = assertCryptoRand(); err != nil { return err } var rootCrt *x509.Certificate var rootKey interface{} caURL := ctx.String("with-ca-url") root := ctx.String("root") key := ctx.String("key") ra := strings.ToLower(ctx.String("ra")) switch { case len(root) > 0 && len(key) == 0: return errs.RequiredWithFlag(ctx, "root", "key") case len(root) == 0 && len(key) > 0: return errs.RequiredWithFlag(ctx, "key", "root") case len(root) > 0 && len(key) > 0: if rootCrt, err = pemutil.ReadCertificate(root); err != nil { return err } if rootKey, err = pemutil.Read(key); err != nil { return err } case ra != "" && ra != apiv1.CloudCAS: return errs.InvalidFlagValue(ctx, "ra", ctx.String("ra"), "CloudCAS") } configure := !ctx.Bool("pki") noDB := ctx.Bool("no-db") if !configure && noDB { return errs.IncompatibleFlagWithFlag(ctx, "pki", "no-db") } var password string if passwordFile := ctx.String("password-file"); passwordFile != "" { password, err = utils.ReadStringPasswordFromFile(passwordFile) if err != nil { return err } } // Provisioner password will be equal to the certificate private keys if // --provisioner-password-file is not provided. var provisionerPassword []byte if passwordFile := ctx.String("provisioner-password-file"); passwordFile != "" { provisionerPassword, err = utils.ReadPasswordFromFile(passwordFile) if err != nil { return err } } var name, org, resource string var casOptions apiv1.Options switch ra { case apiv1.CloudCAS: var create bool var project, location string iss := ctx.String("issuer") if iss == "" { create, err = ui.PromptYesNo("Would you like to create a new PKI (y) or use an existing one (n)?") if err != nil { return err } if create { ui.Println("What would you like to name your new PKI?", ui.WithValue(ctx.String("name"))) name, err = ui.Prompt("(e.g. Smallstep)", ui.WithValidateNotEmpty(), ui.WithValue(ctx.String("name"))) if err != nil { return err } ui.Println("What is the name of your organization?") org, err = ui.Prompt("(e.g. Smallstep)", ui.WithValidateNotEmpty()) if err != nil { return err } ui.Println("What resource id do you want to use? [we will append -Root-CA or -Intermediate-CA]") resource, err = ui.Prompt("(e.g. Smallstep)", ui.WithValidateRegexp("^[a-zA-Z0-9-_]+$")) if err != nil { return err } ui.Println("What is the id of your project on Google's Cloud Platform?") project, err = ui.Prompt("(e.g. smallstep-ca)", ui.WithValidateRegexp("^[a-z][a-z0-9-]{4,28}[a-z0-9]$")) if err != nil { return err } ui.Println("What region or location do you want to use?") location, err = ui.Prompt("(e.g. us-west1)", ui.WithValidateRegexp("^[a-z0-9-]+$")) if err != nil { return err } } else { ui.Println("What certificate authority would you like to use?") iss, err = ui.Prompt("(e.g. projects/smallstep-ca/locations/us-west1/certificateAuthorities/intermediate-ca)", ui.WithValidateRegexp("^projects/[a-z][a-z0-9-]{4,28}[a-z0-9]/locations/[a-z0-9-]+/certificateAuthorities/[a-zA-Z0-9-_]+$")) if err != nil { return err } } } casOptions = apiv1.Options{ Type: apiv1.CloudCAS, CredentialsFile: ctx.String("credentials-file"), CertificateAuthority: iss, IsCreator: create, Project: project, Location: location, } default: ui.Println("What would you like to name your new PKI?", ui.WithValue(ctx.String("name"))) name, err = ui.Prompt("(e.g. Smallstep)", ui.WithValidateNotEmpty(), ui.WithValue(ctx.String("name"))) if err != nil { return err } org = name casOptions = apiv1.Options{ Type: apiv1.SoftCAS, IsCreator: true, } } p, err := pki.New(casOptions) if err != nil { return err } if configure { var names string ui.Println("What DNS names or IP addresses would you like to add to your new CA?", ui.WithValue(ctx.String("dns"))) names, err = ui.Prompt("(e.g. ca.smallstep.com[,1.1.1.1,etc.])", ui.WithValidateFunc(ui.DNS()), ui.WithValue(ctx.String("dns"))) if err != nil { return err } names = strings.Replace(names, " ", ",", -1) parts := strings.Split(names, ",") var dnsNames []string for _, name := range parts { if len(name) == 0 { continue } dnsNames = append(dnsNames, strings.TrimSpace(name)) } var address string ui.Println("What IP and port will your new CA bind to?", ui.WithValue(ctx.String("address"))) address, err = ui.Prompt("(e.g. :443 or 127.0.0.1:4343)", ui.WithValidateFunc(ui.Address()), ui.WithValue(ctx.String("address"))) if err != nil { return err } var provisioner string ui.Println("What would you like to name the CA's first provisioner?", ui.WithValue(ctx.String("provisioner"))) provisioner, err = ui.Prompt("(e.g. you@smallstep.com)", ui.WithValidateNotEmpty(), ui.WithValue(ctx.String("provisioner"))) if err != nil { return err } p.SetProvisioner(provisioner) p.SetAddress(address) p.SetDNSNames(dnsNames) p.SetCAURL(caURL) } ui.Println("Choose a password for your CA keys and first provisioner.", ui.WithValue(password)) pass, err := ui.PromptPasswordGenerate("[leave empty and we'll generate one]", ui.WithRichPrompt(), ui.WithValue(password)) if err != nil { return err } if configure { // Generate provisioner key pairs. if len(provisionerPassword) > 0 { if err = p.GenerateKeyPairs(provisionerPassword); err != nil { return err } } else { if err = p.GenerateKeyPairs(pass); err != nil { return err } } } if casOptions.IsCreator { var root *apiv1.CreateCertificateAuthorityResponse // Generate root certificate if not set. if rootCrt == nil && rootKey == nil { fmt.Println() fmt.Print("Generating root certificate... \n") root, err = p.GenerateRootCertificate(name, org, resource, pass) if err != nil { return err } fmt.Println("all done!") } else { fmt.Println() fmt.Print("Copying root certificate... \n") // Do not copy key in STEPPATH if err = p.WriteRootCertificate(rootCrt, nil, nil); err != nil { return err } root = p.CreateCertificateAuthorityResponse(rootCrt, rootKey) fmt.Println("all done!") } fmt.Println() fmt.Print("Generating intermediate certificate... \n") err = p.GenerateIntermediateCertificate(name, org, resource, root, pass) if err != nil { return err } } else { // Attempt to get the root certificate from RA. if err := p.GetCertificateAuthority(); err != nil { return err } } if ctx.Bool("ssh") { fmt.Println() fmt.Print("Generating user and host SSH certificate signing keys... \n") if err := p.GenerateSSHSigningKeys(pass); err != nil { return err } } fmt.Println("all done!") if !configure { p.TellPKI() return nil } opts := []pki.Option{} if noDB { opts = append(opts, pki.WithoutDB()) } return p.Save(opts...) } // assertCryptoRand asserts that a cryptographically secure random number // generator is available, it will return an error otherwise. func assertCryptoRand() error { buf := make([]byte, 64) _, err := io.ReadFull(rand.Reader, buf) if err != nil { return errs.NewError("crypto/rand is unavailable: Read() failed with %#v", err) } return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/provisioner/000077500000000000000000000000001411117046000241375ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/command/ca/provisioner/add.go000066400000000000000000000570461411117046000252320ustar00rootroot00000000000000package provisioner import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" "encoding/pem" "io/ioutil" "net/url" "strings" "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/jose" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) func addCommand() cli.Command { return cli.Command{ Name: "add", Action: cli.ActionFunc(addAction), Usage: "add one or more provisioners the CA configuration", UsageText: `**step ca provisioner add** [ ...] **--ca-config**= [**--type**=JWK] [**--create**] [**--password-file**=] **step ca provisioner add** **--type**=OIDC **--ca-config**= [**--client-id**=] [**--client-secret**=] [**--configuration-endpoint**=] [**--domain**=] [**--admin**=]... **step ca provisioner add** **--type**=x5c **--x5c-root**= [**--ca-config**=]... **step ca provisioner add** **--type**=k8sSA [**--pem-keys=**] [**--ca-config**=]... **step ca provisioner add** **--type**=[AWS|Azure|GCP] [**--ca-config**=] [**--aws-account**=] [**--gcp-service-account**=] [**--gcp-project**=] [**--azure-tenant**=] [**--azure-resource-group**=] [**--instance-age**=] [**--iid-roots**=] [**--disable-custom-sans**] [**--disable-trust-on-first-use**] **step ca provisioner add** **--type**=ACME **--ca-config**=`, Flags: []cli.Flag{ flags.CaConfig, cli.StringFlag{ Name: "type", Value: provisioner.TypeJWK.String(), Usage: `The of provisioner to create. : is a case-insensitive string and must be one of: **JWK** : Uses an JWK key pair to sign provisioning tokens. (default) **OIDC** : Uses an OpenID Connect provider to sign provisioning tokens. **AWS** : Uses Amazon AWS instance identity documents. **GCP** : Use Google instance identity tokens. **Azure** : Uses Microsoft Azure identity tokens. **ACME** : Uses the ACME protocol to create certificates. **X5C** : Uses an X509 Certificate / private key pair to sign provisioning tokens. **K8sSA** : Uses Kubernetes Service Account tokens. **SSHPOP** : Uses an SSH Certificate / private key pair to sign provisioning tokens.`, }, flags.PasswordFile, cli.BoolFlag{ Name: "create", Usage: `Create a new ECDSA key pair using curve P-256 and populate a new JWK provisioner with it.`, }, cli.BoolFlag{ Name: "ssh", Usage: `Enable SSH on the new provisioners.`, }, // OIDC provisioner flags cli.StringFlag{ Name: "client-id", Usage: `The used to validate the audience in an OpenID Connect token.`, }, cli.StringFlag{ Name: "client-secret", Usage: `The used to obtain the OpenID Connect tokens.`, }, cli.StringFlag{ Name: "listen-address", Usage: `The callback
used in the OpenID Connect flow (e.g. \":10000\")`, }, cli.StringFlag{ Name: "configuration-endpoint", Usage: `OpenID Connect configuration .`, }, cli.StringSliceFlag{ Name: "admin", Usage: `The of an admin user in an OpenID Connect provisioner, this user will not have restrictions in the certificates to sign. Use the '--admin' flag multiple times to configure multiple administrators.`, }, cli.StringSliceFlag{ Name: "domain", Usage: `The used to validate the email claim in an OpenID Connect provisioner. Use the '--domain' flag multiple times to configure multiple domains.`, }, // Cloud provisioner flags cli.StringSliceFlag{ Name: "aws-account", Usage: `The AWS account used to validate the identity documents. Use the flag multiple times to configure multiple accounts.`, }, cli.StringFlag{ Name: "azure-tenant", Usage: `The Microsoft Azure tenant used to validate the identity tokens.`, }, cli.StringSliceFlag{ Name: "azure-resource-group", Usage: `The Microsoft Azure resource group used to validate the identity tokens. Use the flag multipl etimes to configure multiple resource groups`, }, cli.StringSliceFlag{ Name: "gcp-service-account", Usage: `The Google service account or used to validate the identity tokens. Use the flag multiple times to configure multiple service accounts.`, }, cli.StringSliceFlag{ Name: "gcp-project", Usage: `The Google project used to validate the identity tokens. Use the flag multipl etimes to configure multiple projects`, }, cli.DurationFlag{ Name: "instance-age", Usage: `The maximum to grant a certificate in AWS and GCP provisioners. A is sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`, }, cli.StringFlag{ Name: "iid-roots", Usage: `The to the file containing the certificates used to validate the instance identity documents in AWS.`, }, cli.BoolFlag{ Name: "disable-custom-sans", Usage: `On cloud provisioners, if anabled only the internal DNS and IP will be added as a SAN. By default it will accept any SAN in the CSR.`, }, cli.BoolFlag{ Name: "disable-trust-on-first-use,disable-tofu", Usage: `On cloud provisioners, if enabled multiple sign request for this provisioner with the same instance will be accepted. By default only the first request will be accepted.`, }, // X5C provisioner flags cli.StringFlag{ Name: "x5c-root", Usage: `Root certificate (chain) used to validate the signature on X5C provisioning tokens.`, }, // K8sSA provisioner flags cli.StringFlag{ Name: "pem-keys", Usage: `Public key for validating signatures on K8s Service Account Tokens. PEM formatted bundle (can have multiple PEM blocks in the same file) of public keys and x509 Certificates.`, }, }, Description: `**step ca provisioner add** adds one or more provisioners to the configuration and writes the new configuration back to the CA config. To pick up the new configuration you must SIGHUP (kill -1 ) or restart the step-ca process. ## POSITIONAL ARGUMENTS : The name of the provisioners, if a list of JWK files are passed, this name will be linked to all the keys. : List of private (or public) keys in JWK or PEM format. ## EXAMPLES Add a single JWK provisioner: ''' $ step ca provisioner add max@smallstep.com ./max-laptop.jwk --ca-config ca.json ''' Add a single JWK provisioner using an auto-generated asymmetric key pair: ''' $ step ca provisioner add max@smallstep.com --ca-config ca.json \ --create ''' Add a single JWK provisioner with ssh enabled: ''' $ step ca provisioner add max@smallstep.com --ca-config ca.json --ssh --create ''' Add a list of provisioners for a single name: ''' $ step ca provisioner add max@smallstep.com ./max-laptop.jwk ./max-phone.pem ./max-work.pem \ --ca-config ca.json ''' Add a single OIDC provisioner: ''' $ step ca provisioner add Google --type oidc --ca-config ca.json \ --client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com \ --configuration-endpoint https://accounts.google.com/.well-known/openid-configuration ''' Add an OIDC provisioner with two administrators: ''' $ step ca provisioner add Google --type oidc --ca-config ca.json \ --client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com \ --client-secret udTrOT3gzrO7W9fDPgZQLfYJ \ --configuration-endpoint https://accounts.google.com/.well-known/openid-configuration \ --admin mariano@smallstep.com --admin max@smallstep.com \ --domain smallstep.com ''' Add an AWS provisioner on one account with a one hour of instance age: ''' $ step ca provisioner add Amazon --type AWS --ca-config ca.json \ --aws-account 123456789 --instance-age 1h ''' Add an GCP provisioner with two service accounts and two project ids: ''' $ step ca provisioner add Google --type GCP --ca-config ca.json \ --gcp-service-account 1234567890-compute@developer.gserviceaccount.com \ --gcp-service-account 9876543210-compute@developer.gserviceaccount.com \ --gcp-project identity --gcp-project accounting ''' Add an Azure provisioner with two service groups: ''' $ step ca provisioner add Azure --type Azure --ca-config ca.json \ --azure-tenant bc9043e2-b645-4c1c-a87a-78f8644bfe57 \ --azure-resource-group identity --azure-resource-group accounting ''' Add an GCP provisioner that will only accept the SANs provided in the identity token: ''' $ step ca provisioner add Google --type GCP --ca-config ca.json \ --disable-custom-sans --gcp-project internal ''' Add an AWS provisioner that will only accept the SANs provided in the identity document and will allow multiple certificates from the same instance: ''' $ step ca provisioner add Amazon --type AWS --ca-config ca.json \ --aws-account 123456789 --disable-custom-sans --disable-trust-on-first-use ''' Add an AWS provisioner that will use a custom certificate to validate the instance identity documents: ''' $ step ca provisioner add Amazon --type AWS --ca-config ca.json \ --aws-account 123456789 --iid-roots $(step path)/certs/aws.crt ''' Add an ACME provisioner. ''' $ step ca provisioner add acme-smallstep --type ACME ''' Add an X5C provisioner. ''' $ step ca provisioner add x5c-smallstep --type X5C --x5c-root x5cRoot.crt ''' Add a K8s Service Account provisioner. ''' $ step ca provisioner add my-kube-provisioner --type K8sSA --pem-keys keys.pub ''' Add an SSH-POP provisioner. ''' $ step ca provisioner add sshpop-smallstep --type SSHPOP '''`, } } func addAction(ctx *cli.Context) (err error) { if ctx.NArg() == 0 { return errs.TooFewArguments(ctx) } args := ctx.Args() name := args[0] config := ctx.String("ca-config") if len(config) == 0 { return errs.RequiredFlag(ctx, "ca-config") } c, err := authority.LoadConfiguration(config) if err != nil { return errors.Wrapf(err, "error loading configuration") } typ, err := parseProvisionerType(ctx) if err != nil { return err } provMap := make(map[string]bool) for _, p := range c.AuthorityConfig.Provisioners { provMap[p.GetID()] = true } var list provisioner.List switch typ { case provisioner.TypeJWK: list, err = addJWKProvisioner(ctx, name, provMap) case provisioner.TypeOIDC: list, err = addOIDCProvisioner(ctx, name, provMap) case provisioner.TypeAWS: list, err = addAWSProvisioner(ctx, name, provMap) case provisioner.TypeAzure: list, err = addAzureProvisioner(ctx, name, provMap) case provisioner.TypeGCP: list, err = addGCPProvisioner(ctx, name, provMap) case provisioner.TypeACME: list, err = addACMEProvisioner(ctx, name, provMap) case provisioner.TypeX5C: list, err = addX5CProvisioner(ctx, name, provMap) case provisioner.TypeK8sSA: list, err = addK8sSAProvisioner(ctx, name, provMap) case provisioner.TypeSSHPOP: list, err = addSSHPOPProvisioner(ctx, name, provMap) default: return errors.Errorf("unknown type %s: this should not happen", typ) } if err != nil { return err } c.AuthorityConfig.Provisioners = append(c.AuthorityConfig.Provisioners, list...) if err = c.Save(config); err != nil { return err } ui.Println("Success! Your `step-ca` config has been updated. To pick up the new configuration SIGHUP (kill -1 ) or restart the step-ca process.") return nil } func addJWKProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { var password string if passwordFile := ctx.String("password-file"); len(passwordFile) > 0 { password, err = utils.ReadStringPasswordFromFile(passwordFile) if err != nil { return nil, err } } if ctx.Bool("create") { if ctx.NArg() > 1 { return nil, errs.IncompatibleFlag(ctx, "create", " positional arg") } pass, err := ui.PromptPasswordGenerate("Please enter a password to encrypt the provisioner private key? [leave empty and we'll generate one]", ui.WithValue(password)) if err != nil { return nil, err } jwk, jwe, err := jose.GenerateDefaultKeyPair(pass) if err != nil { return nil, err } encryptedKey, err := jwe.CompactSerialize() if err != nil { return nil, errors.Wrap(err, "error serializing private key") } // Create provisioner p := &provisioner.JWK{ Type: provisioner.TypeJWK.String(), Name: name, Key: jwk, EncryptedKey: encryptedKey, Claims: getClaims(ctx), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with name=%s and kid=%s", name, jwk.KeyID) } list = append(list, p) return list, nil } // Add multiple provisioners using JWK files. if ctx.NArg() < 2 { return nil, errs.TooFewArguments(ctx) } jwkFiles := ctx.Args()[1:] for _, filename := range jwkFiles { jwk, err := jose.ParseKey(filename) if err != nil { return nil, errs.FileError(err, filename) } // Only use asymmetric cryptography if _, ok := jwk.Key.([]byte); ok { return nil, errors.New("invalid JWK: a symmetric key cannot be used as a provisioner") } // Create kid if not present if len(jwk.KeyID) == 0 { jwk.KeyID, err = jose.Thumbprint(jwk) if err != nil { return nil, err } } key := jwk.Public() // Initialize provisioner and check for duplicates p := &provisioner.JWK{ Type: provisioner.TypeJWK.String(), Name: name, Key: &key, Claims: getClaims(ctx), } if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with name=%s and kid=%s", name, jwk.KeyID) } // Encrypt JWK if !jwk.IsPublic() { jwe, err := jose.EncryptJWK(jwk) if err != nil { return nil, err } encryptedKey, err := jwe.CompactSerialize() if err != nil { return nil, errors.Wrap(err, "error serializing private key") } p.EncryptedKey = encryptedKey } list = append(list, p) } return list, nil } func addOIDCProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { clientID := ctx.String("client-id") if len(clientID) == 0 { return nil, errs.RequiredWithFlagValue(ctx, "type", ctx.String("type"), "client-id") } confURL := ctx.String("configuration-endpoint") if len(confURL) == 0 { return nil, errs.RequiredWithFlagValue(ctx, "type", ctx.String("type"), "configuration-endpoint") } u, err := url.Parse(confURL) if err != nil || (u.Scheme != "https" && u.Scheme != "http") { return nil, errs.InvalidFlagValue(ctx, "configuration-endpoint", confURL, "") } // Create provisioner p := &provisioner.OIDC{ Type: provisioner.TypeOIDC.String(), Name: name, ClientID: clientID, ClientSecret: ctx.String("client-secret"), ConfigurationEndpoint: confURL, Admins: ctx.StringSlice("admin"), Domains: ctx.StringSlice("domain"), Claims: getClaims(ctx), ListenAddress: ctx.String("listen-address"), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with name=%s and client-id=%s", p.GetName(), p.GetID()) } list = append(list, p) return } func addAWSProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { d, err := parseIntaceAge(ctx) if err != nil { return nil, err } p := &provisioner.AWS{ Type: provisioner.TypeAWS.String(), Name: name, Accounts: ctx.StringSlice("aws-account"), DisableCustomSANs: ctx.Bool("disable-custom-sans"), DisableTrustOnFirstUse: ctx.Bool("disable-trust-on-first-use"), InstanceAge: d, IIDRoots: ctx.String("iid-roots"), Claims: getClaims(ctx), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with type=AWS and name=%s", p.GetName()) } list = append(list, p) return } func addAzureProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { tenantID := ctx.String("azure-tenant") if tenantID == "" { return nil, errs.RequiredWithFlagValue(ctx, "type", ctx.String("type"), "azure-tenant") } p := &provisioner.Azure{ Type: provisioner.TypeAzure.String(), Name: name, TenantID: tenantID, ResourceGroups: ctx.StringSlice("azure-resource-group"), DisableCustomSANs: ctx.Bool("disable-custom-sans"), DisableTrustOnFirstUse: ctx.Bool("disable-trust-on-first-use"), Claims: getClaims(ctx), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with type=Azure and name=%s", p.GetName()) } list = append(list, p) return } func addGCPProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { d, err := parseIntaceAge(ctx) if err != nil { return nil, err } p := &provisioner.GCP{ Type: provisioner.TypeGCP.String(), Name: name, ServiceAccounts: ctx.StringSlice("gcp-service-account"), ProjectIDs: ctx.StringSlice("gcp-project"), DisableCustomSANs: ctx.Bool("disable-custom-sans"), DisableTrustOnFirstUse: ctx.Bool("disable-trust-on-first-use"), InstanceAge: d, Claims: getClaims(ctx), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with type=GCP and name=%s", p.GetName()) } list = append(list, p) return } func addACMEProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { p := &provisioner.ACME{ Type: provisioner.TypeACME.String(), Name: name, Claims: getClaims(ctx), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID==%s", p.GetID()) } list = append(list, p) return } func addX5CProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { x5cRootFile := ctx.String("x5c-root") if len(x5cRootFile) == 0 { return nil, errs.RequiredWithFlagValue(ctx, "type", "x5c", "x5c-root") } roots, err := pemutil.ReadCertificateBundle(x5cRootFile) if err != nil { return nil, errors.Wrapf(err, "error loading X5C Root certificates from %s", x5cRootFile) } var rootBytes []byte for _, r := range roots { if r.KeyUsage&x509.KeyUsageCertSign == 0 { return nil, errors.Errorf("error: certificate with common name '%s' cannot be "+ "used as an X5C root certificate.\n\n"+ "X5C provisioner root certificates must have the 'Certificate Sign' key "+ "usage extension.", r.Subject.CommonName) } rootBytes = append(rootBytes, pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: r.Raw, })...) } p := &provisioner.X5C{ Type: provisioner.TypeX5C.String(), Name: name, Claims: getClaims(ctx), Roots: rootBytes, } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID()) } list = append(list, p) return } // addK8sSAProvisioner returns a provisioner list containing a kubernetes // service account provisioner. // NOTE: step-ca currently only supports one k8sSA provisioner (because we do // not have a good way of distinguishing between tokens), therefore w/e `name` // is entered by the user will be overwritten by a default value. func addK8sSAProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { pemKeysF := ctx.String("pem-keys") if len(pemKeysF) == 0 { return nil, errs.RequiredWithFlagValue(ctx, "type", "k8sSA", "pem-keys") } pemKeysB, err := ioutil.ReadFile(pemKeysF) if err != nil { return nil, errors.Wrap(err, "error reading pem keys") } var ( block *pem.Block rest = pemKeysB pemKeys = []interface{}{} ) for rest != nil { block, rest = pem.Decode(rest) if block == nil { break } key, err := pemutil.ParseKey(pem.EncodeToMemory(block)) if err != nil { return nil, errors.Wrapf(err, "error parsing public key from %s", pemKeysF) } switch q := key.(type) { case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: default: return nil, errors.Errorf("Unexpected public key type %T in %s", q, pemKeysF) } pemKeys = append(pemKeys, key) } var pubKeyBytes []byte for _, k := range pemKeys { blk, err := pemutil.Serialize(k) if err != nil { return nil, errors.Wrap(err, "error serializing pem key") } pubKeyBytes = append(pubKeyBytes, pem.EncodeToMemory(blk)...) } p := &provisioner.K8sSA{ Type: provisioner.TypeK8sSA.String(), Name: name, Claims: getClaims(ctx), PubKeys: pubKeyBytes, } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID()) } list = append(list, p) return } // addSSHPOPProvisioner returns a provisioner list containing a SSHPOP provisioner. func addSSHPOPProvisioner(ctx *cli.Context, name string, provMap map[string]bool) (list provisioner.List, err error) { ctx.Set("ssh", "true") p := &provisioner.SSHPOP{ Type: provisioner.TypeSSHPOP.String(), Name: name, Claims: getClaims(ctx), } // Check for duplicates if _, ok := provMap[p.GetID()]; !ok { provMap[p.GetID()] = true } else { return nil, errors.Errorf("duplicated provisioner: CA config already contains a provisioner with ID=%s", p.GetID()) } list = append(list, p) return } func getClaims(ctx *cli.Context) *provisioner.Claims { if ctx.Bool("ssh") { enable := true return &provisioner.Claims{ EnableSSHCA: &enable, } } return nil } func parseIntaceAge(ctx *cli.Context) (provisioner.Duration, error) { age := ctx.Duration("instance-age") if age == 0 { return provisioner.Duration{}, nil } if age < 0 { return provisioner.Duration{}, errs.MinSizeFlag(ctx, "instance-age", "0s") } return provisioner.Duration{Duration: age}, nil } func parseProvisionerType(ctx *cli.Context) (provisioner.Type, error) { typ := ctx.String("type") switch strings.ToLower(typ) { case "", "jwk": return provisioner.TypeJWK, nil case "oidc": return provisioner.TypeOIDC, nil case "gcp": return provisioner.TypeGCP, nil case "aws": return provisioner.TypeAWS, nil case "azure": return provisioner.TypeAzure, nil case "acme": return provisioner.TypeACME, nil case "x5c": return provisioner.TypeX5C, nil case "sshpop": return provisioner.TypeSSHPOP, nil case "k8ssa": return provisioner.TypeK8sSA, nil default: return 0, errs.InvalidFlagValue(ctx, "type", typ, "JWK, OIDC, AWS, Azure, GCP, ACME, X5C, SSHPOP, K8sSA") } } golang-github-smallstep-cli-0.15.16+ds/command/ca/provisioner/getEncryptedKey.go000066400000000000000000000026451411117046000276030ustar00rootroot00000000000000package provisioner import ( "fmt" "github.com/pkg/errors" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/urfave/cli" ) func getEncryptedKeyCommand() cli.Command { return cli.Command{ Name: "jwe-key", Action: cli.ActionFunc(getEncryptedKeyAction), Usage: "retrieve and print a provisioning key in the CA", UsageText: `**step ca provisioner jwe-key** [**--ca-url**=] [**--root**=]`, Description: `**step ca provisioner jwe-key** returns the encrypted private jwk for the given key-id. ## EXAMPLES Retrieve the encrypted private jwk for the given key-id: ''' $ step ca provisioner jwe-key 1234 --ca-url https://127.0.0.1 --root ./root.crt ''' `, Flags: []cli.Flag{ cli.StringFlag{ Name: "ca-url", Usage: " of the targeted Step Certificate Authority.", }, cli.StringFlag{ Name: "root", Usage: "The path to the PEM used as the root certificate authority.", }, }, } } func getEncryptedKeyAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 1); err != nil { return err } kid := ctx.Args().Get(0) root := ctx.String("root") caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } key, err := pki.GetProvisionerKey(caURL, root, kid) if err != nil { return errors.Wrap(err, "error getting the provisioning key") } fmt.Println(key) return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/provisioner/list.go000066400000000000000000000026411411117046000254440ustar00rootroot00000000000000package provisioner import ( "encoding/json" "fmt" "github.com/pkg/errors" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/urfave/cli" ) func listCommand() cli.Command { return cli.Command{ Name: "list", Action: cli.ActionFunc(listAction), Usage: "list provisioners configured in the CA", UsageText: `**step ca provisioner list** [**--ca-url**=] [**--root**=]`, Flags: []cli.Flag{ cli.StringFlag{ Name: "ca-url", Usage: " of the targeted Step Certificate Authority.", }, cli.StringFlag{ Name: "root", Usage: "The path to the PEM used as the root certificate authority.", }, }, Description: `**step ca provisioner list** lists the provisioners configured in the CA. ## EXAMPLES Prints a JSON list with active provisioners: ''' $ step ca provisioner list '''`, } } func listAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 0); err != nil { return err } root := ctx.String("root") caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } provisioners, err := pki.GetProvisioners(caURL, root) if err != nil { return errors.Wrap(err, "error getting the provisioners") } b, err := json.MarshalIndent(provisioners, "", " ") if err != nil { return errors.Wrap(err, "error marshaling provisioners") } fmt.Println(string(b)) return nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/provisioner/provisioner.go000066400000000000000000000051101411117046000270420ustar00rootroot00000000000000package provisioner import "github.com/urfave/cli" // Command returns the jwk subcommand. func Command() cli.Command { return cli.Command{ Name: "provisioner", Usage: "create and manage the certificate authority provisioners", UsageText: "step ca provisioner [arguments] [global-flags] [subcommand-flags]", Subcommands: cli.Commands{ listCommand(), getEncryptedKeyCommand(), addCommand(), removeCommand(), }, Description: `The **step ca provisioner** command group provides facilities for managing the certificate authority provisioner. A provisioner is an entity that controls provisioning credentials, which are used to generate provisioning tokens. Provisioning credentials are simple JWK key pairs using public-key cryptography. The public key is used to verify a provisioning token while the private key is used to sign the provisioning token. Provisioning tokens are JWT tokens signed by the JWK private key. These JWT tokens are used to get a valid TLS certificate from the certificate authority. Each provisioner is able to manage a different set of rules that can be used to configure the bounds of the certificate. In the certificate authority, a provisioner is configured with a JSON object with the following properties: * **name**: the provisioner name, it will become the JWT issuer and a good practice is to use an email address for this. * **type**: the provisioner type, currently only "jwk" is supported. * **key**: the JWK public key used to verify the provisioning tokens. * **encryptedKey** (optional): the JWE compact serialization of the private key used to sign the provisioning tokens. * **claims** (optional): an object with custom options for each provisioner. Options supported are: * **minTLSCertDuration**: minimum duration of a certificate, set to 5m by default. * **maxTLSCertDuration**: maximum duration of a certificate, set to 24h by default. * **defaultTLSCertDuration**: default duration of the certificate, set to 24h by default. * **disableRenewal**: whether or not to disable certificate renewal, set to false by default. ## EXAMPLES List the active provisioners: ''' $ step ca provisioner list ''' Retrieve the encrypted private jwk for the given kid: ''' $ step ca provisioner jwe-key 1234 --ca-url https://127.0.0.1 --root ./root.crt ''' Add a single provisioner: ''' $ step ca provisioner add max@smallstep.com max-laptop.jwk --ca-config ca.json ''' Remove the provisioner matching a given issuer and kid: ''' $ step ca provisioner remove max@smallstep.com --kid 1234 --ca-config ca.json '''`, } } golang-github-smallstep-cli-0.15.16+ds/command/ca/provisioner/remove.go000066400000000000000000000131261411117046000257660ustar00rootroot00000000000000package provisioner import ( "strings" "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/ui" "github.com/urfave/cli" ) func removeCommand() cli.Command { return cli.Command{ Name: "remove", Action: cli.ActionFunc(removeAction), Usage: "remove one, or more, provisioners from the CA configuration", UsageText: `**step ca provisioner remove** [**--kid**=] [**--config**=] [**--all**]`, Flags: []cli.Flag{ cli.StringFlag{ Name: "ca-config", Usage: "The containing the CA configuration.", }, cli.StringFlag{ Name: "kid", Usage: "The (Key ID) of the JWK provisioner key to be removed.", }, cli.StringFlag{ Name: "client-id", Usage: "The (Client ID) of the OIDC provisioner to be removed.", }, cli.BoolFlag{ Name: "all", Usage: `Remove all provisioners with a given name. Cannot be used in combination w/ the **--kid** or **--client-id** flag.`, }, cli.StringFlag{ Name: "type", Usage: `The of provisioner to remove. Type is a case-insensitive string and must be one of: **JWK** : Uses an JWK key pair to sign provisioning tokens. **OIDC** : Uses an OpenID Connect provider to sign provisioning tokens. **AWS** : Uses Amazon AWS instance identity documents. **GCP** : Use Google instance identity tokens. **Azure** : Uses Microsoft Azure identity tokens. **ACME** : Uses ACME protocol. **X5C** : Uses an X509 Certificate / private key pair to sign provisioning tokens. **K8sSA** : Uses Kubernetes Service Account tokens.`, }, }, Description: `**step ca provisioner remove** removes one or more provisioners from the configuration and writes the new configuration back to the CA config. To pick up the new configuration you must SIGHUP (kill -1 ) or restart the step-ca process. ## POSITIONAL ARGUMENTS : The name field of the provisioner(s) to be removed. ## EXAMPLES Remove all provisioners associated with a given name (max@smallstep.com): ''' $ step ca provisioner remove max@smallstep.com --all --ca-config ca.json ''' Remove the provisioner matching a given name and kid: ''' $ step ca provisioner remove max@smallstep. --kid 1234 --ca-config ca.json ''' Remove the provisioner matching a given name and a client id: ''' $ step ca provisioner remove Google --ca-config ca.json \ --client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com ''' Remove the cloud identity provisioner given name and a type: ''' $ step ca provisioner remove Amazon --ca-config ca.json --type AWS ''' Remove the ACME provisioner by name: ''' $ step ca provisioner remove my-acme-provisioner --type acme ''' Remove an X5C provisioner by name: ''' $ step ca provisioner remove my-x5c-provisioner --type x5c ''' Remove a K8sSA provisioner by name: ''' $ step ca provisioner remove k8sSA-default --type k8sSA '''`, } } func removeAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 1); err != nil { return err } name := ctx.Args().Get(0) config := ctx.String("ca-config") all := ctx.Bool("all") kid := ctx.String("kid") clientID := ctx.String("client-id") typ := ctx.String("type") if len(config) == 0 { return errs.RequiredFlag(ctx, "ca-config") } if len(kid) > 0 && len(clientID) > 0 { return errs.MutuallyExclusiveFlags(ctx, "kid", "client-id") } if all { if len(kid) != 0 { return errs.MutuallyExclusiveFlags(ctx, "all", "kid") } if len(clientID) != 0 { return errs.MutuallyExclusiveFlags(ctx, "all", "client-id") } } else { if len(kid) == 0 && len(clientID) == 0 && len(typ) == 0 { return errs.RequiredOrFlag(ctx, "all", "kid", "client-id", "type") } } c, err := authority.LoadConfiguration(config) if err != nil { return errors.Wrapf(err, "error loading configuration") } var ( provisioners provisioner.List found = false ) for _, p := range c.AuthorityConfig.Provisioners { if p.GetName() != name || !isProvisionerType(p, typ) { provisioners = append(provisioners, p) continue } if !all { switch pp := p.(type) { case *provisioner.JWK: if kid != "" && pp.Key.KeyID != kid { provisioners = append(provisioners, p) } case *provisioner.OIDC: if clientID != "" && pp.ClientID != clientID { provisioners = append(provisioners, p) } case *provisioner.AWS, *provisioner.Azure, *provisioner.GCP, *provisioner.ACME, *provisioner.X5C, *provisioner.K8sSA: // they are filtered by type and name. default: continue } } found = true } if !found { switch { case kid != "": return errors.Errorf("no provisioners with name=%s and kid=%s found", name, kid) case clientID != "": return errors.Errorf("no provisioners with name=%s and client-id=%s found", name, clientID) case typ != "": return errors.Errorf("no provisioners with name=%s and type=%s found", name, typ) default: return errors.Errorf("no provisioners with name %s found", name) } } c.AuthorityConfig.Provisioners = provisioners if err = c.Save(config); err != nil { return err } ui.Println("Success! Your `step-ca` config has been updated. To pick up the new configuration SIGHUP (kill -1 ) or restart the step-ca process.") return nil } // isProvisionerType returns true if p.GetType() is equal to typ. If typ is // empty it will always return true. func isProvisionerType(p provisioner.Interface, typ string) bool { return typ == "" || strings.EqualFold(typ, p.GetType().String()) } golang-github-smallstep-cli-0.15.16+ds/command/ca/renew.go000066400000000000000000000367531411117046000232450ustar00rootroot00000000000000package ca import ( "crypto" "crypto/tls" "crypto/x509" "encoding/pem" "io/ioutil" "log" "math/rand" "net/http" "os" "os/exec" "os/signal" "strconv" "strings" "syscall" "time" "github.com/pkg/errors" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils/cautils" "github.com/smallstep/cli/utils/sysutils" "github.com/urfave/cli" ) func renewCertificateCommand() cli.Command { return cli.Command{ Name: "renew", Action: command.ActionFunc(renewCertificateAction), Usage: "renew a valid certificate", UsageText: `**step ca renew** [**--ca-url**=] [**--root**=] [**--password-file**=] [**--out**=] [**--expires-in**=] [**--force**] [**--expires-in**=] [**--pid**=] [**--pid-file**=] [**--signal**=] [**--exec**=] [**--daemon**] [**--renew-period**=]`, Description: ` **step ca renew** command renews the given certificate (with a request to the certificate authority) and writes the new certificate to disk - either overwriting or using a new file when the **--out**= flag is used. With the **--daemon** flag the command will periodically update the given certificate. By default, it will renew the certificate before 2/3 of the validity period of the certificate has elapsed. A random jitter is used to avoid multiple instances running at the same time. The amount of time between renewal and certificate expiration can be configured using the **--expires-in** flag, or a fixed period can be set with the **--renew-period** flag. The **--daemon** flag can be combined with **--pid**, **--signal**, or **--exec** to provide certificate reloads on your services. ## POSITIONAL ARGUMENTS : The certificate in PEM format that we want to renew. : They key file of the certificate. ## EXAMPLES Renew a certificate with the configured CA: ''' $ step ca renew internal.crt internal.key Would you like to overwrite internal.crt [Y/n]: y ''' Renew a certificate without overwriting the previous certificate: ''' $ step ca renew --out renewed.crt internal.crt internal.key ''' Renew a certificate forcing the overwrite of the previous certificate: ''' $ step ca renew --force internal.crt internal.key ''' Renew a certificate providing the <--ca-url> and <--root> flags: ''' $ step ca renew --ca-url https://ca.smallstep.com:9000 \ --root /path/to/root_ca.crt internal.crt internal.key Would you like to overwrite internal.crt [Y/n]: y ''' Renew skipped because it was too early: ''' $ step ca renew --expires-in 8h internal.crt internal.key certificate not renewed: expires in 10h52m5s ''' Renew the certificate before 2/3 of the validity has passed: ''' $ step ca renew --daemon internal.crt internal.key ''' Renew the certificate before 8 hours and 30m of the expiration time: ''' $ step ca renew --daemon --expires-in 8h30m internal.crt internal.key ''' Renew the certificate every 16h: ''' $ step ca renew --daemon --renew-period 16h internal.crt internal.key ''' Renew the certificate and reload nginx: ''' $ step ca renew --daemon --exec "nginx -s reload" internal.crt internal.key ''' Renew the certificate and convert it to DER: ''' $ step ca renew --daemon --renew-period 16h \ --exec "step certificate format --force --out internal.der internal.crt" \ internal.crt internal.key ''' Renew a certificate using the offline mode, requires the configuration files, certificates, and keys created with **step ca init**: ''' $ step ca renew --offline internal.crt internal.key '''`, Flags: []cli.Flag{ flags.CaConfig, flags.CaURL, flags.Force, flags.Offline, flags.PasswordFile, flags.Root, cli.StringFlag{ Name: "out,output-file", Usage: "The new certificate path. Defaults to overwriting the positional argument", }, cli.StringFlag{ Name: "expires-in", Usage: `The amount of time remaining before certificate expiration, at which point a renewal should be attempted. The certificate renewal will not be performed if the time to expiration is greater than the **--expires-in** value. A random jitter (duration/20) will be added to avoid multiple services hitting the renew endpoint at the same time. The is a sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`, }, cli.IntFlag{ Name: "pid", Usage: `The process id to signal after the certificate has been renewed. By default the the SIGHUP (1) signal will be used, but this can be configured with the **--signal** flag.`, }, cli.StringFlag{ Name: "pid-file", Usage: `The from which to read the process id that will be signaled after the certificate has been renewed. By default the the SIGHUP (1) signal will be used, but this can be configured with the **--signal** flag.`, }, cli.IntFlag{ Name: "signal", Usage: `The signal to send to the selected PID, so it can reload the configuration and load the new certificate. Default value is SIGHUP (1)`, Value: int(syscall.SIGHUP), }, cli.StringFlag{ Name: "exec", Usage: "The to run after the certificate has been renewed.", }, cli.BoolFlag{ Name: "daemon", Usage: `Run the renew command as a daemon, renewing and overwriting the certificate periodically. By default the daemon will renew a certificate before 2/3 of the time to expiration has elapsed. The period can be configured using the **--renew-period** or **--expires-in** flags.`, }, cli.StringFlag{ Name: "renew-period", Usage: `The period with which to schedule renewals of the certificate in daemon mode. Requires the **--daemon** flag. The is a sequence of decimal numbers, each with optional fraction and a unit suffix, such as "300ms", "1.5h", or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`, }, }, } } func renewCertificateAction(ctx *cli.Context) error { err := errs.NumberOfArguments(ctx, 2) if err != nil { return err } args := ctx.Args() certFile := args.Get(0) keyFile := args.Get(1) passFile := ctx.String("password-file") isDaemon := ctx.Bool("daemon") execCmd := ctx.String("exec") outFile := ctx.String("out") if len(outFile) == 0 { outFile = certFile } rootFile := ctx.String("root") if len(rootFile) == 0 { rootFile = pki.GetRootCAPath() } caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } var expiresIn, renewPeriod time.Duration if s := ctx.String("expires-in"); len(s) > 0 { if expiresIn, err = time.ParseDuration(s); err != nil { return errs.InvalidFlagValue(ctx, "expires-in", s, "") } } if s := ctx.String("renew-period"); len(s) > 0 { if renewPeriod, err = time.ParseDuration(s); err != nil { return errs.InvalidFlagValue(ctx, "renew-period", s, "") } } if expiresIn > 0 && renewPeriod > 0 { return errs.IncompatibleFlagWithFlag(ctx, "expires-in", "renew-period") } if renewPeriod > 0 && !isDaemon { return errs.RequiredWithFlag(ctx, "renew-period", "daemon") } if ctx.IsSet("pid") && ctx.IsSet("pid-file") { return errs.MutuallyExclusiveFlags(ctx, "pid", "pid-file") } pid := ctx.Int("pid") if ctx.IsSet("pid") && pid <= 0 { return errs.InvalidFlagValue(ctx, "pid", strconv.Itoa(pid), "") } pidFile := ctx.String("pid-file") if len(pidFile) > 0 { pidB, err := ioutil.ReadFile(pidFile) if err != nil { return errs.FileError(err, pidFile) } pid, err = strconv.Atoi(strings.TrimSpace(string(pidB))) if err != nil { return errs.Wrap(err, "error converting %s to integer process id", pidB) } if pid <= 0 { return errs.InvalidFlagValue(ctx, "pid-file", strconv.Itoa(pid), "") } } signum := ctx.Int("signal") if ctx.IsSet("signal") && signum <= 0 { return errs.InvalidFlagValue(ctx, "signal", strconv.Itoa(signum), "") } cert, err := tlsLoadX509KeyPair(certFile, keyFile, passFile) if err != nil { return err } leaf := cert.Leaf if leaf.NotAfter.Before(time.Now()) { return errors.New("cannot renew an expired certificate") } cvp := leaf.NotAfter.Sub(leaf.NotBefore) if renewPeriod > 0 && renewPeriod >= cvp { return errors.Errorf("flag '--renew-period' must be within (lower than) the certificate "+ "validity period; renew-period=%v, cert-validity-period=%v", renewPeriod, cvp) } renewer, err := newRenewer(ctx, caURL, cert, rootFile) if err != nil { return err } afterRenew := getAfterRenewFunc(pid, signum, execCmd) if isDaemon { // Force is always enabled when daemon mode is used ctx.Set("force", "true") next := nextRenewDuration(leaf, expiresIn, renewPeriod) return renewer.Daemon(outFile, next, expiresIn, renewPeriod, afterRenew) } // Do not renew if (cert.notAfter - now) > (expiresIn + jitter) if expiresIn > 0 { jitter := rand.Int63n(int64(expiresIn / 20)) if d := time.Until(leaf.NotAfter); d > expiresIn+time.Duration(jitter) { ui.Printf("certificate not renewed: expires in %s\n", d.Round(time.Second)) return nil } } if _, err := renewer.Renew(outFile); err != nil { return err } ui.Printf("Your certificate has been saved in %s.\n", outFile) return afterRenew() } func nextRenewDuration(leaf *x509.Certificate, expiresIn, renewPeriod time.Duration) time.Duration { if renewPeriod > 0 { // Renew now if it will be expired in renewPeriod if (time.Until(leaf.NotAfter) - renewPeriod) <= 0 { return 0 } return renewPeriod } period := leaf.NotAfter.Sub(leaf.NotBefore) if expiresIn == 0 { expiresIn = period / 3 } d := time.Until(leaf.NotAfter) - expiresIn n := rand.Int63n(int64(period / 20)) d -= time.Duration(n) if d < 0 { d = 0 } return d } func getAfterRenewFunc(pid, signum int, execCmd string) func() error { return func() error { if err := runKillPid(pid, signum); err != nil { return err } return runExecCmd(execCmd) } } func runKillPid(pid, signum int) error { if pid == 0 { return nil } if err := sysutils.Kill(pid, syscall.Signal(signum)); err != nil { return errors.Wrapf(err, "kill %d with signal %d failed", pid, signum) } return nil } func runExecCmd(execCmd string) error { execCmd = strings.TrimSpace(execCmd) if execCmd == "" { return nil } parts := strings.Split(execCmd, " ") cmd := exec.Command(parts[0], parts[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } type renewer struct { client cautils.CaClient transport *http.Transport key crypto.PrivateKey offline bool } func newRenewer(ctx *cli.Context, caURL string, cert tls.Certificate, rootFile string) (*renewer, error) { if len(cert.Certificate) == 0 { return nil, errors.New("error loading certificate: certificate chain is empty") } rootCAs, err := x509util.ReadCertPool(rootFile) if err != nil { return nil, err } tr := &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: rootCAs, PreferServerCipherSuites: true, }, } var client cautils.CaClient offline := ctx.Bool("offline") if offline { caConfig := ctx.String("ca-config") if caConfig == "" { return nil, errs.InvalidFlagValue(ctx, "ca-config", "", "") } client, err = cautils.NewOfflineCA(caConfig) if err != nil { return nil, err } } else { client, err = ca.NewClient(caURL, ca.WithTransport(tr)) if err != nil { return nil, err } } return &renewer{ client: client, transport: tr, key: cert.PrivateKey, offline: offline, }, nil } func (r *renewer) Renew(outFile string) (*api.SignResponse, error) { resp, err := r.client.Renew(r.transport) if err != nil { return nil, errors.Wrap(err, "error renewing certificate") } if resp.CertChainPEM == nil || len(resp.CertChainPEM) == 0 { resp.CertChainPEM = []api.Certificate{resp.ServerPEM, resp.CaPEM} } var data []byte for _, certPEM := range resp.CertChainPEM { pemblk, err := pemutil.Serialize(certPEM.Certificate) if err != nil { return nil, errors.Wrap(err, "error serializing certificate PEM") } data = append(data, pem.EncodeToMemory(pemblk)...) } if err := utils.WriteFile(outFile, data, 0600); err != nil { return nil, errs.FileError(err, outFile) } return resp, nil } // RenewAndPrepareNext renews the cert and prepares the cert for it's next renewal. // NOTE: this function logs each time the certificate is successfully renewed. func (r *renewer) RenewAndPrepareNext(outFile string, expiresIn, renewPeriod time.Duration) (time.Duration, error) { const durationOnErrors = 1 * time.Minute Info := log.New(os.Stdout, "INFO: ", log.LstdFlags) resp, err := r.Renew(outFile) if err != nil { return durationOnErrors, err } x509Chain, err := pemutil.ReadCertificateBundle(outFile) if err != nil { return durationOnErrors, errs.Wrap(err, "error reading certificate chain") } x509ChainBytes := make([][]byte, len(x509Chain)) for i, c := range x509Chain { x509ChainBytes[i] = c.Raw } cert := tls.Certificate{ Certificate: x509ChainBytes, PrivateKey: r.key, Leaf: x509Chain[0], } if len(cert.Certificate) == 0 { return durationOnErrors, errors.New("error loading certificate: certificate chain is empty") } // Prepare next transport r.transport.TLSClientConfig.Certificates = []tls.Certificate{cert} // Get next renew duration next := nextRenewDuration(resp.ServerPEM.Certificate, expiresIn, renewPeriod) Info.Printf("%s certificate renewed, next in %s", resp.ServerPEM.Certificate.Subject.CommonName, next.Round(time.Second)) return next, nil } func (r *renewer) Daemon(outFile string, next, expiresIn, renewPeriod time.Duration, afterRenew func() error) error { // Loggers Info := log.New(os.Stdout, "INFO: ", log.LstdFlags) Error := log.New(os.Stderr, "ERROR: ", log.LstdFlags) // Daemon loop signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) defer signal.Stop(signals) Info.Printf("first renewal in %s", next.Round(time.Second)) for { select { case sig := <-signals: switch sig { case syscall.SIGHUP: if _, err := r.RenewAndPrepareNext(outFile, expiresIn, renewPeriod); err != nil { Error.Println(err) } else if err := afterRenew(); err != nil { Error.Println(err) } case syscall.SIGINT, syscall.SIGTERM: return nil } case <-time.After(next): if _, err := r.RenewAndPrepareNext(outFile, expiresIn, renewPeriod); err != nil { Error.Println(err) } else if err := afterRenew(); err != nil { Error.Println(err) } } } } func tlsLoadX509KeyPair(certFile, keyFile, passFile string) (tls.Certificate, error) { x509Chain, err := pemutil.ReadCertificateBundle(certFile) if err != nil { return tls.Certificate{}, errs.Wrap(err, "error reading certificate chain") } x509ChainBytes := make([][]byte, len(x509Chain)) for i, c := range x509Chain { x509ChainBytes[i] = c.Raw } opts := []pemutil.Options{pemutil.WithFilename(keyFile)} if passFile != "" { opts = append(opts, pemutil.WithPasswordFile(passFile)) } pk, err := pemutil.Read(keyFile, opts...) if err != nil { return tls.Certificate{}, errs.Wrap(err, "error parsing private key") } return tls.Certificate{ Certificate: x509ChainBytes, PrivateKey: pk, Leaf: x509Chain[0], }, nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/revoke.go000066400000000000000000000362031411117046000234060ustar00rootroot00000000000000package ca import ( "crypto/tls" "crypto/x509" "encoding/pem" "io/ioutil" "net/http" "os" "strconv" "strings" "time" "github.com/pkg/errors" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/crypto/x509util" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/jose" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils/cautils" "github.com/urfave/cli" "golang.org/x/crypto/ocsp" ) /* NOTE: This command currently only supports passive revocation. Passive revocation means preventing a certificate from being renewed and letting it expire. TODO: Add support for CRL and OCSP. */ func revokeCertificateCommand() cli.Command { return cli.Command{ Name: "revoke", Action: command.ActionFunc(revokeCertificateAction), Usage: "revoke a certificate", UsageText: `**step ca revoke** [**--cert**=] [**--key**=] [**--token**=] [**--ca-url**=] [**--root**=] [**--reason**=] [**--reasonCode**=] [**-offline**]`, Description: ` **step ca revoke** command revokes a certificate with the given serial number. **Active Revocation**: A certificate is no longer valid from the moment it has been actively revoked. Clients are required to check against centralized sources of certificate validity information (e.g. by using CRLs (Certificate Revocation Lists) or OCSP (Online Certificate Status Protocol)) to verify that certificates have not been revoked. Active Revocation requires clients to take an active role in certificate validation for the benefit of real time revocation. **Passive Revocation**: A certificate that has been passively revoked can no longer be renewed. It will still be valid for the remainder of it's validity period, but cannot be prolonged. The benefit of passive revocation is that clients can verify certificates in a simple, decentralized manner without relying on centralized 3rd parties. Passive revocation works best with short certificate lifetimes. **step ca revoke** currently only supports passive revocation. Active revocation is on our roadmap. ## POSITIONAL ARGUMENTS : The serial number of the certificate that should be revoked. Can be left blank either to be supplied by prompt or when using --cert and --key flags for revocation over mTLS. ## EXAMPLES Revoke a certificate using a transparently generated API token and the default 'unspecified' reason: ''' $ step ca revoke 308893286343609293989051180431574390766 ''' Revoke a certificate using a transparently generated token and configured reason and reasonCode: ''' $ step ca revoke --reason "laptop compromised" --reasonCode 1 308893286343609293989051180431574390766 ''' Revoke a certificate using a transparently generated token and configured reason and stringified reasonCode: ''' $ step ca revoke --reason "laptop compromised" --reasonCode "key compromise" 308893286343609293989051180431574390766 ''' Revoke a certificate using that same certificate to validate and authorize the request (rather than a token) over mTLS: ''' $ step ca revoke --cert mike.cert --key mike.key ''' Revoke a certificate using a token, generated by a provisioner, to authorize the request with the CA: ''' $ TOKEN=$(step ca token --revoke 308893286343609293989051180431574390766) $ step ca revoke --token $TOKEN 308893286343609293989051180431574390766 ''' Revoke a certificate in offline mode: ''' $ step ca revoke --offline 308893286343609293989051180431574390766 ''' Revoke a certificate in offline mode using --cert and --key (the cert/key pair will be validated against the root and intermediate certifcates configured in the step CA): ''' $ step ca revoke --offline --cert foo.crt --key foo.key '''`, Flags: []cli.Flag{ cli.StringFlag{ Name: "cert", Usage: `The path to the that should be revoked.`, }, cli.StringFlag{ Name: "key", Usage: `The to the key corresponding to the cert that should be revoked.`, }, cli.StringFlag{ Name: "reason", Usage: `The representing the reason for which the cert is being revoked.`, }, cli.StringFlag{ Name: "reasonCode", Value: "", Usage: `The specifies the reason for revocation - chose from a list of common revocation reasons. If unset, the default is Unspecified. : can be a number from 0-9 or a case insensitive string matching one of the following options: **Unspecified** : No reason given (Default -- reasonCode=0). **KeyCompromise** : The key is believed to have been compromised (reasonCode=1). **CACompromise** : The issuing Certificate Authority itself has been compromised (reasonCode=2). **AffiliationChanged** : The certificate contained affiliation information, for example, it may have been an EV certificate and the associated business is no longer owned by the same entity (reasonCode=3). **Superseded** : The certificate is being replaced (reasonCode=4). **CessationOfOperation** : If a CA is decommissioned, no longer to be used, the CA's certificate should be revoked with this reason code. Do not revoke the CA's certificate if the CA no longer issues new certificates, yet still publishes CRLs for the currently issued certificates (reasonCode=5). **CertificateHold** : A temporary revocation that indicates that a CA will not vouch for a certificate at a specific point in time. Once a certificate is revoked with a CertificateHold reason code, the certificate can then be revoked with another Reason Code, or unrevoked and returned to use (reasonCode=6). **RemoveFromCRL** : If a certificate is revoked with the CertificateHold reason code, it is possible to "unrevoke" a certificate. The unrevoking process still lists the certificate in the CRL, but with the reason code set to RemoveFromCRL. Note: This is specific to the CertificateHold reason and is only used in DeltaCRLs (reasonCode=8). **PrivilegeWithdrawn** : The right to represent the given entity was revoked for some reason (reasonCode=9). **AACompromise** : It is known or suspected that aspects of the AA validated in the attribute certificate have been compromised (reasonCode=10). `, }, flags.CaConfig, flags.CaURL, flags.Offline, flags.Root, flags.Token, }, } } func revokeCertificateAction(ctx *cli.Context) error { args := ctx.Args() serial := args.Get(0) certFile, keyFile := ctx.String("cert"), ctx.String("key") token := ctx.String("token") offline := ctx.Bool("offline") // Validate the reasonCode arg early in the flow. if _, err := ReasonCodeToNum(ctx.String("reasonCode")); err != nil { return err } // offline and token are incompatible because the token is generated before // the start of the offline CA. if offline && len(token) != 0 { return errs.IncompatibleFlagWithFlag(ctx, "offline", "token") } // revokeFlow unifies online and offline flows on a single api. flow, err := newRevokeFlow(ctx, certFile, keyFile) if err != nil { return err } // If cert and key are passed then infer the serial number and certificate // that should be revoked. if len(certFile) > 0 || len(keyFile) > 0 { // Must be using cert/key flags for mTLS revoke so should be 0 cmd line args. if ctx.NArg() > 0 { return errors.Errorf("'%s %s --cert --key ' expects no additional positional arguments", ctx.App.Name, ctx.Command.Name) } if len(certFile) == 0 { return errs.RequiredWithFlag(ctx, "key", "cert") } if len(keyFile) == 0 { return errs.RequiredWithFlag(ctx, "cert", "key") } if len(token) > 0 { errs.IncompatibleFlagWithFlag(ctx, "cert", "token") } if len(serial) > 0 { errs.IncompatibleFlagWithFlag(ctx, "cert", "serial") } var cert []*x509.Certificate cert, err = pemutil.ReadCertificateBundle(certFile) if err != nil { return err } serial = cert[0].SerialNumber.String() } else { // Must be using serial number so verify that only 1 command line args was given. if err = errs.NumberOfArguments(ctx, 1); err != nil { return err } if len(token) == 0 { // No token and no cert/key pair - so generate a token. token, err = flow.GenerateToken(ctx, &serial) if err != nil { return err } } } if err := flow.Revoke(ctx, serial, token); err != nil { return err } ui.Printf("Certificate with Serial Number %s has been revoked.\n", serial) return nil } type revokeTokenClaims struct { SHA string `json:"sha"` jose.Claims } type revokeFlow struct { offlineCA *cautils.OfflineCA offline bool } func newRevokeFlow(ctx *cli.Context, certFile, keyFile string) (*revokeFlow, error) { var err error var offlineClient *cautils.OfflineCA offline := ctx.Bool("offline") if offline { caConfig := ctx.String("ca-config") if caConfig == "" { return nil, errs.InvalidFlagValue(ctx, "ca-config", "", "") } offlineClient, err = cautils.NewOfflineCA(caConfig) if err != nil { return nil, err } if len(certFile) > 0 || len(keyFile) > 0 { if err = offlineClient.VerifyClientCert(certFile, keyFile); err != nil { return nil, err } } } return &revokeFlow{ offlineCA: offlineClient, offline: offline, }, nil } func (f *revokeFlow) getClient(ctx *cli.Context, serial, token string) (cautils.CaClient, error) { if f.offline { return f.offlineCA, nil } // Create online client caURL, err := flags.ParseCaURLIfExists(ctx) if err != nil { return nil, err } rootFile := ctx.String("root") var options []ca.ClientOption if len(token) > 0 { tok, err := jose.ParseSigned(token) if err != nil { return nil, errors.Wrap(err, "error parsing flag '--token'") } var claims revokeTokenClaims if err := tok.UnsafeClaimsWithoutVerification(&claims); err != nil { return nil, errors.Wrap(err, "error parsing flag '--token'") } if !strings.EqualFold(claims.Subject, serial) { return nil, errors.Errorf("token subject '%s' and serial number '%s' do not match", claims.Subject, serial) } // Prepare client for bootstrap or provisioning tokens if len(claims.SHA) > 0 && len(claims.Audience) > 0 && strings.HasPrefix(strings.ToLower(claims.Audience[0]), "http") { if len(caURL) == 0 { caURL = claims.Audience[0] } options = append(options, ca.WithRootSHA256(claims.SHA)) ui.PrintSelected("CA", caURL) return ca.NewClient(caURL, options...) } } else { // If there is no token then caURL is required. if len(caURL) == 0 { return nil, errs.RequiredFlag(ctx, "ca-url") } } if len(rootFile) == 0 { rootFile = pki.GetRootCAPath() if _, err := os.Stat(rootFile); err != nil { return nil, errs.RequiredFlag(ctx, "root") } } options = append(options, ca.WithRootFile(rootFile)) ui.PrintSelected("CA", caURL) return ca.NewClient(caURL, options...) } func (f *revokeFlow) GenerateToken(ctx *cli.Context, subject *string) (string, error) { // For offline just generate the token if f.offline { return f.offlineCA.GenerateToken(ctx, cautils.RevokeType, *subject, nil, time.Time{}, time.Time{}, provisioner.TimeDuration{}, provisioner.TimeDuration{}) } // Use online CA to get the provisioners and generate the token caURL, err := flags.ParseCaURLIfExists(ctx) if err != nil { return "", err } else if len(caURL) == 0 { return "", errs.RequiredUnlessFlag(ctx, "ca-url", "token") } root := ctx.String("root") if len(root) == 0 { root = pki.GetRootCAPath() if _, err := os.Stat(root); err != nil { return "", errs.RequiredUnlessFlag(ctx, "root", "token") } } if *subject == "" { *subject, err = ui.Prompt("What is the Serial Number of the certificate you would like to revoke? (`step certificate inspect foo.cert`)", ui.WithValidateNotEmpty()) if err != nil { return "", err } } return cautils.NewTokenFlow(ctx, cautils.RevokeType, *subject, nil, caURL, root, time.Time{}, time.Time{}, provisioner.TimeDuration{}, provisioner.TimeDuration{}) } func (f *revokeFlow) Revoke(ctx *cli.Context, serial, token string) error { client, err := f.getClient(ctx, serial, token) if err != nil { return err } reason := ctx.String("reason") // Convert the reasonCode flag to an OCSP revocation code. reasonCode, err := ReasonCodeToNum(ctx.String("reasonCode")) if err != nil { return err } var tr http.RoundTripper // If token is not provided then set up mTLS client with expected cert and key. if len(token) == 0 { certFile, keyFile := ctx.String("cert"), ctx.String("key") certPEMBytes, err := ioutil.ReadFile(certFile) if err != nil { return errors.Wrap(err, "error reading certificate") } key, err := pemutil.Read(keyFile) if err != nil { return errors.Wrap(err, "error parsing key") } keyBlock, err := pemutil.Serialize(key) if err != nil { return errors.Wrap(err, "error serializing key") } cert, err := tls.X509KeyPair(certPEMBytes, pem.EncodeToMemory(keyBlock)) if err != nil { return errors.Wrap(err, "error loading certificate key pair") } if len(cert.Certificate) == 0 { return errors.New("error loading certificate: certificate chain is empty") } root := ctx.String("root") if len(root) == 0 { root = pki.GetRootCAPath() if _, err = os.Stat(root); err != nil { return errs.RequiredUnlessFlag(ctx, "root", "token") } } var rootCAs *x509.CertPool rootCAs, err = x509util.ReadCertPool(root) if err != nil { return err } tr = &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ RootCAs: rootCAs, PreferServerCipherSuites: true, Certificates: []tls.Certificate{cert}, }, } } req := &api.RevokeRequest{ Serial: serial, Reason: reason, ReasonCode: reasonCode, OTT: token, Passive: true, } if _, err = client.Revoke(req, tr); err != nil { return err } return nil } // RevocationReasonCodes is a map between string reason codes // to integers as defined in RFC 5280 var RevocationReasonCodes = map[string]int{ "unspecified": ocsp.Unspecified, "keycompromise": ocsp.KeyCompromise, "cacompromise": ocsp.CACompromise, "affiliationchanged": ocsp.AffiliationChanged, "superseded": ocsp.Superseded, "cessationofoperation": ocsp.CessationOfOperation, "certificatehold": ocsp.CertificateHold, "removefromcrl": ocsp.RemoveFromCRL, "privilegewithdrawn": ocsp.PrivilegeWithdrawn, "aacompromise": ocsp.AACompromise, } // ReasonCodeToNum converts a string encoded code to a number. // 1) "4" -> 4 // 2) "key compromise" -> 1 // 3) "keYComPromIse" -> 1 func ReasonCodeToNum(rc string) (int, error) { // default to 0 if rc == "" { return 0, nil } if code, err := strconv.Atoi(rc); err == nil { if code < ocsp.Unspecified || code > ocsp.AACompromise { return -1, errors.Errorf("reasonCode out of bounds. Got %d, but want value between %d and %d", code, ocsp.Unspecified, ocsp.AACompromise) } return code, nil } code, found := RevocationReasonCodes[strings.ToLower(strings.Replace(rc, " ", "", -1))] if !found { return 0, errors.Errorf("unrecognized revocation reason code '%s'", rc) } return code, nil } golang-github-smallstep-cli-0.15.16+ds/command/ca/root.go000066400000000000000000000053421411117046000230760ustar00rootroot00000000000000package ca import ( "crypto/tls" "encoding/pem" "fmt" "net/http" "github.com/pkg/errors" "github.com/smallstep/certificates/ca" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/ui" "github.com/urfave/cli" ) func rootComand() cli.Command { return cli.Command{ Name: "root", Action: command.ActionFunc(rootAction), Usage: "download and validate the root certificate", UsageText: `**step ca root** [] [**--ca-url**=] [**--fingerprint**=]`, Description: `**step ca root** downloads and validates the root certificate from the certificate authority. ## POSITIONAL ARGUMENTS : File to write root certificate (PEM format) ## EXAMPLES Get the root fingerprint in the CA: ''' $ step certificate fingerprint /path/to/root_ca.crt 0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3 ''' Download the root certificate from the configured certificate authority: ''' $ step ca root root_ca.crt \ --fingerprint 0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3 ''' Download the root certificate using a given certificate authority: ''' $ step ca root root_ca.crt \ --ca-url https://ca.smallstep.com:9000 \ --fingerprint 0d7d3834cf187726cf331c40a31aa7ef6b29ba4df601416c9788f6ee01058cf3 ''' Print the root certificate using the flags set by : ''' $ step ca root '''`, Flags: []cli.Flag{ flags.CaURL, flags.Force, fingerprintFlag, }, } } func rootAction(ctx *cli.Context) error { if err := errs.MinMaxNumberOfArguments(ctx, 0, 1); err != nil { return err } caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } fingerprint := ctx.String("fingerprint") if len(fingerprint) == 0 { return errs.RequiredFlag(ctx, "fingerprint") } tr := getInsecureTransport() client, err := ca.NewClient(caURL, ca.WithTransport(tr)) if err != nil { return err } // Root already validates the certificate resp, err := client.Root(fingerprint) if err != nil { return errors.Wrap(err, "error downloading root certificate") } if rootFile := ctx.Args().Get(0); rootFile != "" { if _, err := pemutil.Serialize(resp.RootPEM.Certificate, pemutil.ToFile(rootFile, 0600)); err != nil { return err } ui.Printf("The root certificate has been saved in %s.\n", rootFile) } else { block, err := pemutil.Serialize(resp.RootPEM.Certificate) if err != nil { return err } fmt.Print(string(pem.EncodeToMemory(block))) } return nil } func getInsecureTransport() *http.Transport { return &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } } golang-github-smallstep-cli-0.15.16+ds/command/ca/sign.go000066400000000000000000000153531411117046000230560ustar00rootroot00000000000000package ca import ( "crypto/x509" "strings" "github.com/pkg/errors" "github.com/smallstep/certificates/api" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/token" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils/cautils" "github.com/urfave/cli" ) func signCertificateCommand() cli.Command { return cli.Command{ Name: "sign", Action: command.ActionFunc(signCertificateAction), Usage: "generate a new certificate signing a certificate request", UsageText: `**step ca sign** [**--token**=] [**--issuer**=] [**--provisioner-password-file=] [**--not-before**=] [**--not-after**=] [**--ca-url**=] [**--root**=] [**--set**=] [**--set-file**=] [**--acme**=] [**--standalone**] [**--webroot**=] [**--contact**=] [**--http-listen**=
] [**--console**] [**--x5c-cert**=] [**--x5c-key**=] [**--k8ssa-token-path**=]`, Description: `**step ca sign** command signs the given csr and generates a new certificate. ## POSITIONAL ARGUMENTS : File with the certificate signing request (PEM format) : File to write the certificate (PEM format) ## EXAMPLES Sign a new certificate for the given CSR: ''' $ TOKEN=$(step ca token internal.example.com) $ step ca sign --token $TOKEN internal.csr internal.crt ''' Sign a new certificate with a 1h validity: ''' $ TOKEN=$(step ca token internal.example.com) $ step ca sign --token $TOKEN --not-after=1h internal.csr internal.crt ''' Sign a new certificate using the offline mode, requires the configuration files, certificates, and keys created with **step ca init**: ''' $ step ca sign --offline internal internal.csr internal.crt ''' Sign a new certificate using an X5C provisioner: NOTE: You must have a X5C provisioner configured (using **step ca provisioner add**). ''' $ step ca sign foo.internal foo.csr foo.crt --x5c-cert leaf-x5c.crt --x5c-key leaf-x5c.key ''' **Certificate Templates** - With a provisioner configured with a custom template we can use the **--set** flag to pass user variables: ''' $ step ca sign foo.csr foo.crt --set dnsNames=foo.internal.com $ step ca sign foo.csr foo.crt --set dnsNames='["foo.internal.com","bar.internal.com"]' ''' Or you can pass them from a file using **--set-file**: ''' $ cat path/to/data.json { "dnsNames": ["foo.internal.com","bar.internal.com"] } $ step ca sign foo.csr foo.crt --set-file path/to/data.json ''' **step CA ACME** - In order to use the step CA ACME protocol you must add a ACME provisioner to the step CA config. See **step ca provisioner add -h**. Sign a CSR using the step CA ACME server and a standalone server to serve the challenges locally (standalone mode is the default): ''' $ step ca sign foo.csr foo.crt --provisioner my-acme-provisioner ''' Sign a CSR using the step CA ACME server and an existing server along with webroot mode to serve the challenges locally: ''' $ step ca sign foo.csr foo.crt \ --provisioner my-acme-provisioner --webroot "./acme-www" \ ''' Sign a CSR using the ACME protocol served by another online CA (not step CA, e.g. letsencrypt). NOTE: Let's Encrypt requires that the Subject Common Name of a requested certificate be validated as an Identifier in the ACME order along with any other SANS. Therefore, the Common Name must be a valid DNS Name. The step CA does not impose this requirement. ''' $ step ca sign foo.csr foo.crt \ --acme https://acme-staging-v02.api.letsencrypt.org/directory '''`, Flags: []cli.Flag{ flags.CaConfig, flags.CaURL, flags.Root, flags.Token, flags.Provisioner, flags.ProvisionerPasswordFile, flags.NotBefore, flags.NotAfter, flags.TemplateSet, flags.TemplateSetFile, flags.Force, flags.Offline, consoleFlag, flags.X5cCert, flags.X5cKey, acmeFlag, acmeStandaloneFlag, acmeWebrootFlag, acmeContactFlag, acmeHTTPListenFlag, flags.K8sSATokenPathFlag, }, } } func signCertificateAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 2); err != nil { return err } args := ctx.Args() csrFile := args.Get(0) crtFile := args.Get(1) tok := ctx.String("token") offline := ctx.Bool("offline") csrInt, err := pemutil.Read(csrFile) if err != nil { return err } csr, ok := csrInt.(*x509.CertificateRequest) if !ok { return errors.Errorf("error parsing %s: file is not a certificate request", csrFile) } if err = csr.CheckSignature(); err != nil { return errors.Wrapf(err, "csr has invalid signature") } // offline and token are incompatible because the token is generated before // the start of the offline CA. if offline && len(tok) != 0 { return errs.IncompatibleFlagWithFlag(ctx, "offline", "token") } // certificate flow unifies online and offline flows on a single api flow, err := cautils.NewCertificateFlow(ctx) if err != nil { return err } if len(tok) == 0 { // Use the ACME protocol with a different certificate authority. if ctx.IsSet("acme") { return cautils.ACMESignCSRFlow(ctx, csr, crtFile, "") } sans := mergeSans(ctx, csr) if tok, err = flow.GenerateToken(ctx, csr.Subject.CommonName, sans); err != nil { switch k := err.(type) { // Use the ACME flow with the step certificate authority. case *cautils.ErrACMEToken: return cautils.ACMESignCSRFlow(ctx, csr, crtFile, k.Name) default: return err } } } // Validate common name jwt, err := token.ParseInsecure(tok) if err != nil { return errors.Wrap(err, "error parsing flag '--token'") } switch jwt.Payload.Type() { case token.AWS, token.GCP, token.Azure, token.K8sSA: // Common name will be validated on the server side, it depends on // server configuration. default: if !strings.EqualFold(jwt.Payload.Subject, csr.Subject.CommonName) { return errors.Errorf("token subject '%s' and CSR CommonName '%s' do not match", jwt.Payload.Subject, csr.Subject.CommonName) } } // Sign if err := flow.Sign(ctx, tok, api.NewCertificateRequest(csr), crtFile); err != nil { return err } ui.PrintSelected("Certificate", crtFile) return nil } func mergeSans(ctx *cli.Context, csr *x509.CertificateRequest) []string { uniq := make([]string, 0) m := make(map[string]bool) for _, s := range ctx.StringSlice("san") { if _, ok := m[s]; !ok { uniq = append(uniq, s) m[s] = true } } for _, s := range csr.DNSNames { if _, ok := m[s]; !ok { uniq = append(uniq, s) m[s] = true } } for _, ip := range csr.IPAddresses { s := ip.String() if _, ok := m[s]; !ok { uniq = append(uniq, s) m[s] = true } } for _, s := range csr.EmailAddresses { if _, ok := m[s]; !ok { uniq = append(uniq, s) m[s] = true } } return uniq } golang-github-smallstep-cli-0.15.16+ds/command/ca/token.go000066400000000000000000000223141411117046000232310ustar00rootroot00000000000000package ca import ( "fmt" "os" "github.com/smallstep/certificates/api" "github.com/smallstep/certificates/pki" "github.com/smallstep/cli/command" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/utils" "github.com/smallstep/cli/utils/cautils" "github.com/urfave/cli" ) func tokenCommand() cli.Command { // Avoid the conflict with --not-before --not-after certNotBeforeFlag := flags.NotBefore certNotAfterFlag := flags.NotAfter certNotBeforeFlag.Name = "cert-not-before" certNotAfterFlag.Name = "cert-not-after" return cli.Command{ Name: "token", Action: command.ActionFunc(tokenAction), Usage: "generate an OTT granting access to the CA", UsageText: `**step ca token** [--**kid**=] [--**issuer**=] [**--ca-url**=] [**--root**=] [**--cert-not-before**=] [**--cert-not-after**=] [**--not-before**=] [**--not-after**=] [**--password-file**=] [**--output-file**=] [**--key**=] [**--san**=] [**--offline**] [**--revoke**] [**--x5c-cert**=] [**--x5c-key**=] [**--sshpop-cert**=] [**--sshpop-key**=] [**--ssh**] [**--host**] [**--principal**=] [**--k8ssa-token-path**=`, Description: `**step ca token** command generates a one-time token granting access to the certificates authority. ## POSITIONAL ARGUMENTS : The Common Name, DNS Name, or IP address that will be set by the certificate authority. When there are no additional Subject Alternative Names configured (via the --san flag), the subject will be added as the only element of the 'sans' claim on the token. ## EXAMPLES Most of the following examples assumes that **--ca-url** and **--root** are set using environment variables or the default configuration file in <$STEPPATH/config/defaults.json>. Get a new token for a DNS. Because there are no Subject Alternative Names configured (via the '--san' flag), the 'sans' claim of the token will have a default value of ['internal.example.com']: ''' $ step ca token internal.example.com ''' Get a new token for a 'Revoke' request: ''' $ step ca token --revoke 146103349666685108195655980390445292315 ''' Get a new token for an IP address. Because there are no Subject Alternative Names configured (via the '--san' flag), the 'sans' claim of the token will have a default value of ['192.168.10.10']: ''' $ step ca token 192.168.10.10 ''' Get a new token with custom Subject Alternative Names. The value of the 'sans' claim of the token will be ['1.1.1.1', 'hello.example.com'] - 'foobar' will not be in the 'sans' claim unless explicitly configured via the '--san' flag: ''' $ step ca token foobar --san 1.1.1.1 --san hello.example.com ''' Get a new token that expires in 30 minutes: ''' $ step ca token --not-after 30m internal.example.com ''' Get a new token that becomes valid in 30 minutes and expires 5 minutes after that: ''' $ step ca token --not-before 30m --not-after 35m internal.example.com ''' Get a new token signed with the given private key, the public key must be configured in the certificate authority: ''' $ step ca token internal.smallstep.com --key token.key ''' Get a new token for a specific provisioner kid, ca-url and root: ''' $ step ca token internal.example.com \ --kid 4vn46fbZT68Uxfs9LBwHkTvrjEvxQqx-W8nnE-qDjts \ --ca-url https://ca.example.com \ --root /path/to/root_ca.crt ''' Get a new token using the simple offline mode, requires the configuration files, certificates, and keys created with **step ca init**: ''' $ step ca token internal.example.com --offline ''' Get a new token using the offline mode with all the parameters: ''' $ step ca token internal.example.com \ --offline \ --kid 4vn46fbZT68Uxfs9LBwHkTvrjEvxQqx-W8nnE-qDjts \ --issuer you@example.com \ --key provisioner.key \ --ca-url https://ca.example.com \ --root /path/to/root_ca.crt ''' Get a new token for a 'Revoke' request: ''' $ step ca token --revoke 146103349666685108195655980390445292315 ''' Get a new token in offline mode for a 'Revoke' request: ''' $ step ca token --offline --revoke 146103349666685108195655980390445292315 ''' Get a new token for an SSH user certificate: ''' $ step ca token max@smallstep.com --ssh ''' Get a new token for an SSH host certificate: ''' $ step ca token my-remote.hostname --ssh --host '''`, Flags: []cli.Flag{ certNotAfterFlag, certNotBeforeFlag, provisionerKidFlag, cli.StringSliceFlag{ Name: "san", Usage: `Add Subject Alternative Name(s) (SANs) that should be authorized. A certificate signing request using this token must match the complete set of SANs in the token 1:1. Use the '--san' flag multiple times to configure multiple SANs.`, }, cli.StringSliceFlag{ Name: "principal,n", Usage: `Add the principals (user or host s) that the token is authorized to request. The signing request using this token won't be able to add extra names. Use the '--principal' flag multiple times to configure multiple principals.`, }, sshHostFlag, flags.CaURL, flags.CaConfig, flags.Force, flags.NotAfter, flags.NotBefore, flags.Offline, flags.Root, flags.Provisioner, flags.ProvisionerPasswordFileWithAlias, flags.X5cCert, flags.X5cKey, flags.SSHPOPCert, flags.SSHPOPKey, cli.StringFlag{ Name: "key", Usage: `The private key used to sign the JWT. This is usually downloaded from the certificate authority.`, }, cli.StringFlag{ Name: "output-file", Usage: "The destination of the generated one-time token.", }, cli.BoolFlag{ Name: "revoke", Usage: `Create a token for authorizing 'Revoke' requests. The audience will be invalid for any other API request.`, }, cli.BoolFlag{ Name: "renew", Usage: `Create a token for authorizing 'renew' requests. The audience will be invalid for any other API request.`, }, cli.BoolFlag{ Name: "rekey", Usage: `Create a token for authorizing 'rekey' requests. The audience will be invalid for any other API request.`, }, cli.BoolFlag{ Name: "ssh", Usage: `Create a token for authorizing an SSH certificate signing request.`, }, flags.K8sSATokenPathFlag, }, } } func tokenAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 1); err != nil { return err } subject := ctx.Args().Get(0) outputFile := ctx.String("output-file") offline := ctx.Bool("offline") // x.509 flags sans := ctx.StringSlice("san") isRevoke := ctx.Bool("revoke") isRenew := ctx.Bool("renew") isRekey := ctx.Bool("rekey") // ssh flags isSSH := ctx.Bool("ssh") isHost := ctx.Bool("host") principals := ctx.StringSlice("principal") switch { case isSSH && len(sans) > 0: return errs.IncompatibleFlagWithFlag(ctx, "ssh", "san") case isHost && len(sans) > 0: return errs.IncompatibleFlagWithFlag(ctx, "host", "san") case len(principals) > 0 && len(sans) > 0: return errs.IncompatibleFlagWithFlag(ctx, "principal", "san") case !isSSH && isHost: return errs.RequiredWithFlag(ctx, "host", "ssh") case !isSSH && len(principals) > 0: return errs.RequiredWithFlag(ctx, "principal", "ssh") } // Default token type is always a 'Sign' token. var typ int if isSSH { switch { case isRevoke: typ = cautils.SSHRevokeType case isRenew: typ = cautils.SSHRenewType case isRekey: typ = cautils.SSHRekeyType case isHost: typ = cautils.SSHHostSignType sans = principals default: typ = cautils.SSHUserSignType sans = principals } } else { switch { case isRevoke: typ = cautils.RevokeType default: typ = cautils.SignType } } caURL, err := flags.ParseCaURL(ctx) if err != nil { return err } root := ctx.String("root") if len(root) == 0 { root = pki.GetRootCAPath() if _, err := os.Stat(root); err != nil { return errs.RequiredFlag(ctx, "root") } } // --san and --type revoke are incompatible. Revocation tokens do not support SANs. if typ == cautils.RevokeType && len(sans) > 0 { return errs.IncompatibleFlagWithFlag(ctx, "san", "revoke") } // parse times or durations notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before")) if !ok { return errs.InvalidFlagValue(ctx, "not-before", ctx.String("not-before"), "") } notAfter, ok := flags.ParseTimeOrDuration(ctx.String("not-after")) if !ok { return errs.InvalidFlagValue(ctx, "not-after", ctx.String("not-after"), "") } // parse certificates durations certNotBefore, err := api.ParseTimeDuration(ctx.String("cert-not-before")) if err != nil { return errs.InvalidFlagValue(ctx, "cert-not-before", ctx.String("cert-not-before"), "") } certNotAfter, err := api.ParseTimeDuration(ctx.String("cert-not-after")) if err != nil { return errs.InvalidFlagValue(ctx, "cert-not-after", ctx.String("cert-not-after"), "") } var token string if offline { token, err = cautils.OfflineTokenFlow(ctx, typ, subject, sans, notBefore, notAfter, certNotBefore, certNotAfter) if err != nil { return err } } else { token, err = cautils.NewTokenFlow(ctx, typ, subject, sans, caURL, root, notBefore, notAfter, certNotBefore, certNotAfter) if err != nil { return err } } if len(outputFile) > 0 { return utils.WriteFile(outputFile, []byte(token), 0600) } fmt.Println(token) return nil } golang-github-smallstep-cli-0.15.16+ds/command/certificate/000077500000000000000000000000001411117046000234575ustar00rootroot00000000000000golang-github-smallstep-cli-0.15.16+ds/command/certificate/bundle.go000066400000000000000000000041271411117046000252630ustar00rootroot00000000000000package certificate import ( "encoding/pem" "io/ioutil" "github.com/pkg/errors" "github.com/smallstep/cli/command" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/urfave/cli" ) func bundleCommand() cli.Command { return cli.Command{ Name: "bundle", Action: command.ActionFunc(bundleAction), Usage: `bundle a certificate with intermediate certificate(s) needed for certificate path validation`, UsageText: `**step certificate bundle** `, Description: `**step certificate bundle** bundles a certificate with any intermediates necessary to validate the certificate. ## POSITIONAL ARGUMENTS : The path to a leaf certificate to bundle with issuing certificate(s). : The path to the Certificate Authority issusing certificate. : The path to write the bundle. ## EXIT CODES This command returns 0 on success and \>0 if any error occurs. ## EXAMPLES Bundle a certificate with the intermediate certificate authority (issuer): ''' $ step certificate bundle foo.crt intermediate-ca.crt foo-bundle.crt ''' `, Flags: []cli.Flag{flags.Force}, } } func bundleAction(ctx *cli.Context) error { if err := errs.NumberOfArguments(ctx, 3); err != nil { return err } crtFile := ctx.Args().Get(0) crtBytes, err := ioutil.ReadFile(crtFile) if err != nil { return errs.FileError(err, crtFile) } crtBlock, _ := pem.Decode(crtBytes) if crtBlock == nil { return errors.Errorf("could not parse certificate file '%s'", crtFile) } caFile := ctx.Args().Get(1) caBytes, err := ioutil.ReadFile(caFile) if err != nil { return errs.FileError(err, caFile) } caBlock, _ := pem.Decode(caBytes) if caBlock == nil { return errors.Errorf("could not parse certificate file '%s'", caFile) } chainFile := ctx.Args().Get(2) if err := utils.WriteFile(chainFile, append(pem.EncodeToMemory(crtBlock), pem.EncodeToMemory(caBlock)...), 0600); err != nil { return err } ui.Printf("Your certificate has been saved in %s.\n", chainFile) return nil } golang-github-smallstep-cli-0.15.16+ds/command/certificate/certificate.go000066400000000000000000000047441411117046000263010ustar00rootroot00000000000000package certificate import ( "github.com/smallstep/cli/command" "github.com/urfave/cli" ) // Command returns the cli.Command for jwt and related subcommands. func init() { cmd := cli.Command{ Name: "certificate", Usage: "create, revoke, validate, bundle, and otherwise manage certificates", UsageText: "step certificate SUBCOMMAND [ARGUMENTS] [GLOBAL_FLAGS] [SUBCOMMAND_FLAGS]", Description: `**step certificate** command group provides facilities for creating certificate signing requests (CSRs), creating self-signed certificates (e.g., for use as a root certificate authority), generating leaf or intermediate CA certificate by signing a CSR, validating certificates, renewing certificates, generating certificate bundles, and key-wrapping of private keys. ## EXAMPLES Create a root certificate and private key using the default parameters (EC P-256 curve): ''' $ step certificate create foo foo.crt foo.key --profile root-ca ''' Create a leaf certificate and private key using the default parameters (EC P-256 curve): ''' $ step certificate create baz baz.crt baz.key --ca ./foo.crt --ca-key ./foo.key ''' Create a CSR and private key using the default parameters (EC P-256 curve): ''' $ step certificate create zap zap.csr zap.key --csr ''' Sign a CSR and generate a signed certificate: ''' $ step certificate sign zap.csr foo.crt foo.key ''' Inspect the contents of a certificate: ''' $ step certificate inspect ./baz.crt ''' Verify the signature of a certificate: ''' $ step certificate verify ./baz.crt --roots ./foo.crt ''' Lint the contents of a certificate to check for common errors and missing fields: ''' $ step certificate lint ./baz.crt ''' Bundle an end certificate with the issuing certificate: ''' $ step certificate bundle ./baz.crt ./foo.crt bundle.crt ''' Convert PEM format certificate to DER and write to disk. ''' $ step certificate format foo.pem --out foo.der ''' Extract the public key from a PEM encoded certificate: ''' $ step certificate key foo.crt ''' Install a root certificate in the system truststore: ''' $ step certificate install root-ca.crt ''' Uninstall a root certificate from the system truststore: ''' $ step certificate uninstall root-ca.crt '''`, Subcommands: cli.Commands{ bundleCommand(), createCommand(), formatCommand(), inspectCommand(), fingerprintCommand(), lintCommand(), signCommand(), verifyCommand(), keyCommand(), installCommand(), uninstallCommand(), p12Command(), }, } command.Register(cmd) } golang-github-smallstep-cli-0.15.16+ds/command/certificate/create.go000066400000000000000000000567201411117046000252630ustar00rootroot00000000000000package certificate import ( "crypto" "crypto/x509" "encoding/pem" "time" "github.com/pkg/errors" "github.com/smallstep/cli/command" "github.com/smallstep/cli/crypto/keys" "github.com/smallstep/cli/crypto/pemutil" "github.com/smallstep/cli/errs" "github.com/smallstep/cli/flags" "github.com/smallstep/cli/ui" "github.com/smallstep/cli/utils" "github.com/urfave/cli" "go.step.sm/crypto/x509util" ) const ( // Supported profiles profileLeaf = "leaf" profileSelfSigned = "self-signed" profileIntermediateCA = "intermediate-ca" profileRootCA = "root-ca" profileCSR = "csr" // Used only on sign // Default durations defaultLeafValidity = 24 * time.Hour defaultSelfSignedValidity = 24 * time.Hour defaultIntermediateValidity = time.Hour * 24 * 365 * 10 defaultRootValidity = time.Hour * 24 * 365 * 10 defaultTemplatevalidity = 24 * time.Hour ) func createCommand() cli.Command { return cli.Command{ Name: "create", Action: command.ActionFunc(createAction), Usage: "create a certificate or certificate signing request", UsageText: `**step certificate create** [**--csr**] [**--profile**=] [**--template**=] [**--not-before**=] [**--not-after**=] [**--password-file**=] [**--ca**=] [**--ca-key**=] [**--ca-password-file**=] [**--san**=] [**--bundle**] [**--key**=] [**--kty**=] [**--curve**=] [**--size**=] [**--no-password**]`, Description: `**step certificate create** generates a certificate or a certificate signing request (CSR) that can be signed later using 'step certificate sign' (or some other tool) to produce a certificate. By default this command creates x.509 certificates or CSRs for use with TLS. If you need something else, you can customize the output using templates. See **TEMPLATES** below. ## POSITIONAL ARGUMENTS : The subject of the certificate. Typically this is a hostname for services or an email address for people. : File to write CRT or CSR to (PEM format) : File to write private key to (PEM format). This argument is optional if **--key** is passed. ## EXIT CODES This command returns 0 on success and \>0 if any error occurs. ## TEMPLATES With templates, you can customize the generated certificate or CSR. Templates are JSON files representing a [certificate](https://pkg.go.dev/go.step.sm/crypto/x509util?tab=doc#Certificate) [1] or a [certificate request](https://pkg.go.dev/go.step.sm/crypto/x509util?tab=doc#CertificateRequest) [2]. They use Golang's [](https://golang.org/pkg/text/template/) package [3] and [](https://masterminds.github.io/sprig/) functions [4]. Here's the default template used for generating a leaf certificate: ''' { "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] } ''' And this is the default template for a CSR: ''' { "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }} } ''' In a custom template, you can change the **subject**, **dnsNames**, **emailAddresses**, **ipAddresses**, and **uris**, and you can add custom x.509 **extensions** or set the **signatureAlgorithm**. For certificate templates, the common extensions **keyUsage**, **extKeyUsage**, and **basicConstraints** are also represented as JSON fields. Two variables are available in templates: **.Subject** contains the argument, and **.SANs** contains the SANs provided with the **--san** flag. Both .Subject and .SANs are objects, and they must be converted to JSON to be used in the template, you can do this using Sprig's **toJson** function. On the .Subject object you can access the common name string using the template variable **.Subject.CommonName**. In **EXAMPLES** below, you can see how these variables are used in a certificate request. For more information on the template properties and functions see: '''raw [1] https://pkg.go.dev/go.step.sm/crypto/x509util?tab=doc#Certificate [2] https://pkg.go.dev/go.step.sm/crypto/x509util?tab=doc#CertificateRequest [3] https://golang.org/pkg/text/template/ [4] https://masterminds.github.io/sprig/ ''' ## EXAMPLES Create a CSR and key: ''' $ step certificate create foo foo.csr foo.key --csr ''' Create a CSR using an existing private key: ''' $ step certificate create --csr --key key.priv foo foo.csr ''' Create a CSR using an existing encrypted private key: ''' $ step certificate create --csr --key key.priv --password-file key.pass foo foo.csr ''' Create a CSR and key with custom Subject Alternative Names: ''' $ step certificate create foo foo.csr foo.key --csr \ --san inter.smallstep.com --san 1.1.1.1 --san ca.smallstep.com ''' Create a CSR and key - do not encrypt the key when writing to disk: ''' $ step certificate create foo foo.csr foo.key --csr --no-password --insecure ''' Create a root certificate and key: ''' $ step certificate create root-ca root-ca.crt root-ca.key --profile root-ca ''' Create an intermediate certificate and key: ''' $ step certificate create intermediate-ca intermediate-ca.crt intermediate-ca.key \ --profile intermediate-ca --ca ./root-ca.crt --ca-key ./root-ca.key ''' Create a leaf certificate and key: ''' $ step certificate create foo foo.crt foo.key --profile leaf \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key ''' Create a leaf certificate and encrypt the private key: ''' $ step certificate create foo foo.crt foo.key --profile leaf \ --password-file ./leaf.pass \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key ''' Create a leaf certificate and decrypt the CA private key: ''' $ step certificate create foo foo.crt foo.key --profile leaf \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key --ca-password-file ./intermediate.pass ''' Create a leaf certificate and key with custom Subject Alternative Names: ''' $ step certificate create foo foo.crt foo.key --profile leaf \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key \ --san inter.smallstep.com --san 1.1.1.1 --san ca.smallstep.com ''' Create a leaf certificate and key with custom validity: ''' $ step certificate create foo foo.crt foo.key --profile leaf \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key \ --not-before 24h --not-after 2160h ''' Create a self-signed leaf certificate and key: ''' $ step certificate create self-signed-leaf.local leaf.crt leaf.key --profile self-signed --subtle ''' Create a root certificate and key with underlying OKP Ed25519: ''' $ step certificate create root-ca root-ca.crt root-ca.key --profile root-ca \ --kty OKP --curve Ed25519 ''' Create an intermediate certificate and key with underlying EC P-256 key pair: ''' $ step certificate create intermediate-ca intermediate-ca.crt intermediate-ca.key \ --profile intermediate-ca --ca ./root-ca.crt --ca-key ./root-ca.key --kty EC --curve P-256 ''' Create a leaf certificate and key with underlying RSA 2048 key pair: ''' $ step certificate create foo foo.crt foo.key --profile leaf \ --ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key --kty RSA --size 2048 ''' Create a CSR and key with underlying OKP Ed25519: ''' $ step certificate create foo foo.csr foo.key --csr --kty OKP --curve Ed25519 ''' Create a root certificate using a custom template. The root certificate will have a path length constraint that allows at least 2 intermediates: ''' $ cat root.tpl { "subject": { "commonName": "Acme Corporation Root CA" }, "issuer": { "commonName": "Acme Corporation Root CA" }, "keyUsage": ["certSign", "crlSign"], "basicConstraints": { "isCA": true, "maxPathLen": 2 } } $ step certificate create --template root.tpl \ "Acme Corporation Root CA" root_ca.crt root_ca_key ''' Create an intermediate certificate using the previous root. This intermediate will be able to sign also new intermediate certificates: ''' $ cat intermediate.tpl { "subject": { "commonName": "Acme Corporation Intermediate CA" }, "keyUsage": ["certSign", "crlSign"], "basicConstraints": { "isCA": true, "maxPathLen": 1 } } $ step certificate create --template intermediate.tpl \ --ca root_ca.crt --ca-key root_ca_key \ "Acme Corporation Intermediate CA" intermediate_ca.crt intermediate_ca_key ''' Sign a new intermediate using the previous intermediate, now with path length 0 using the **--profile** flag: ''' $ step certificate create --profile intermediate-ca \ --ca intermediate_ca.crt --ca-key intermediate_ca_key \ "Coyote Corporation" coyote_ca.crt coyote_ca_key ''' Create a leaf certificate, that is the default profile and bundle it with the two intermediate certificates and validate it: ''' $ step certificate create --ca coyote_ca.crt --ca-key coyote_ca_key \ "coyote@acme.corp" leaf.crt coyote.key $ cat leaf.crt coyote_ca.crt intermediate_ca.crt > coyote.crt $ step certificate verify --roots root_ca.crt coyote.crt ''' Create a certificate request using a template: ''' $ cat csr.tpl { "subject": { "country": "US", "organization": "Coyote Corporation", "commonName": "{{ .Subject.CommonName }}" }, "sans": {{ toJson .SANs }} } $ step certificate create --csr --template csr.tpl --san coyote@acme.corp \ "Wile E. Coyote" coyote.csr coyote.key '''`, Flags: []cli.Flag{ cli.BoolFlag{ Name: "csr", Usage: `Generate a certificate signing request (CSR) instead of a certificate.`, }, cli.StringFlag{ Name: "profile", Value: profileLeaf, Usage: `The certificate profile sets various certificate details such as certificate use and expiration. The default profile is 'leaf' which is suitable for a client or server using TLS. : is a case-sensitive string and must be one of: **leaf** : Generate a leaf x.509 certificate suitable for use with TLS. **intermediate-ca** : Generate a certificate that can be used to sign additional leaf certificates. **root-ca** : Generate a new self-signed root certificate suitable for use as a root CA. **self-signed** : Generate a new self-signed leaf certificate suitable for use with TLS. This profile requires the **--subtle** flag because the use of self-signed leaf certificates is discouraged unless absolutely necessary.`, }, cli.StringFlag{ Name: "template", Usage: `The certificate template , a JSON representation of the certificate to create.`, }, cli.StringFlag{ Name: "password-file", Usage: `The to the file containing the password to encrypt the new private key or decrypt the user submitted private key.`, }, cli.StringFlag{ Name: "ca", Usage: `The certificate authority used to issue the new certificate (PEM file).`, }, cli.StringFlag{ Name: "ca-key", Usage: `The certificate authority private key used to sign the new certificate (PEM file).`, }, cli.StringFlag{ Name: "ca-password-file", Usage: `The to the file containing the password to decrypt the CA private key.`, }, cli.StringFlag{ Name: "key", Usage: "The of the private key to use instead of creating a new one (PEM file).", }, cli.BoolFlag{ Name: "no-password", Usage: `Do not ask for a password to encrypt the private key. Sensitive key material will be written to disk unencrypted. This is not recommended. Requires **--insecure** flag.`, }, cli.StringFlag{ Name: "not-before", Usage: `The set in the NotBefore property of the certificate. If a