fix(auth): prevent user enumeration attack [EE-6832] (#11591)

pull/11655/head 2.19.5
Matt Hook 2024-04-17 16:09:05 +12:00 committed by GitHub
parent 6f5d9c357f
commit 9191d31e92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 456 additions and 101 deletions

176
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,176 @@
name: ci
on:
workflow_dispatch:
push:
branches:
- 'develop'
- 'release/*'
pull_request:
branches:
- 'develop'
- 'release/*'
- 'feat/*'
- 'fix/*'
- 'refactor/*'
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
DOCKER_HUB_REPO: portainerci/portainer-ce
EXTENSION_HUB_REPO: portainerci/portainer-docker-extension
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs:
build_images:
strategy:
matrix:
config:
- { platform: linux, arch: amd64, version: "" }
- { platform: linux, arch: arm64, version: "" }
- { platform: linux, arch: arm, version: "" }
- { platform: linux, arch: ppc64le, version: "" }
- { platform: linux, arch: s390x, version: "" }
- { platform: windows, arch: amd64, version: 1809 }
- { platform: windows, arch: amd64, version: ltsc2022 }
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: '[preparation] checkout the current branch'
uses: actions/checkout@v4.1.1
with:
ref: ${{ github.event.inputs.branch }}
- name: '[preparation] set up golang'
uses: actions/setup-go@v5.0.0
with:
go-version: ${{ env.GO_VERSION }}
- name: '[preparation] set up node.js'
uses: actions/setup-node@v4.0.1
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: '[preparation] set up qemu'
uses: docker/setup-qemu-action@v3.0.0
- name: '[preparation] set up docker context for buildx'
run: docker context create builders
- name: '[preparation] set up docker buildx'
uses: docker/setup-buildx-action@v3.0.0
with:
endpoint: builders
- name: '[preparation] docker login'
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: '[preparation] set the container image tag'
run: |
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
# use the release branch name as the tag for release branches
# for instance, release/2.19 becomes 2.19
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -d "/" -f 2)
elif [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
# use pr${{ github.event.number }} as the tag for pull requests
# for instance, pr123
CONTAINER_IMAGE_TAG="pr${{ github.event.number }}"
else
# replace / with - in the branch name
# for instance, feature/1.0.0 -> feature-1.0.0
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | sed 's/\//-/g')
fi
echo "CONTAINER_IMAGE_TAG=${CONTAINER_IMAGE_TAG}-${{ matrix.config.platform }}${{ matrix.config.version }}-${{ matrix.config.arch }}" >> $GITHUB_ENV
- name: '[execution] build linux & windows portainer binaries'
run: |
export YARN_VERSION=$(yarn --version)
export WEBPACK_VERSION=$(yarn list webpack --depth=0 | grep webpack | awk -F@ '{print $2}')
export BUILDNUMBER=${GITHUB_RUN_NUMBER}
GIT_COMMIT_HASH_LONG=${{ github.sha }}
export GIT_COMMIT_HASH_SHORT={GIT_COMMIT_HASH_LONG:0:7}
NODE_ENV="testing"
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
NODE_ENV="production"
fi
make build-all PLATFORM=${{ matrix.config.platform }} ARCH=${{ matrix.config.arch }} ENV=${NODE_ENV}
env:
CONTAINER_IMAGE_TAG: ${{ env.CONTAINER_IMAGE_TAG }}
- name: '[execution] build and push docker images'
run: |
if [ "${{ matrix.config.platform }}" == "windows" ]; then
mv dist/portainer dist/portainer.exe
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} --build-arg OSVERSION=${{ matrix.config.version }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
else
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" -f build/${{ matrix.config.platform }}/alpine.Dockerfile .
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}" -f build/${{ matrix.config.platform }}/Dockerfile .
docker buildx build --output=type=registry --platform ${{ matrix.config.platform }}/${{ matrix.config.arch }} -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" -f build/${{ matrix.config.platform }}/alpine.Dockerfile .
fi
fi
env:
CONTAINER_IMAGE_TAG: ${{ env.CONTAINER_IMAGE_TAG }}
build_manifests:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
needs: [build_images]
steps:
- name: '[preparation] docker login'
uses: docker/login-action@v3.0.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: '[preparation] set up docker context for buildx'
run: docker version && docker context create builders
- name: '[preparation] set up docker buildx'
uses: docker/setup-buildx-action@v3.0.0
with:
endpoint: builders
- name: '[execution] build and push manifests'
run: |
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
# use the release branch name as the tag for release branches
# for instance, release/2.19 becomes 2.19
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -d "/" -f 2)
elif [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
# use pr${{ github.event.number }} as the tag for pull requests
# for instance, pr123
CONTAINER_IMAGE_TAG="pr${{ github.event.number }}"
else
# replace / with - in the branch name
# for instance, feature/1.0.0 -> feature-1.0.0
CONTAINER_IMAGE_TAG=$(echo $GITHUB_REF_NAME | sed 's/\//-/g')
fi
docker buildx imagetools create -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-ppc64le" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-s390x" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-windows1809-amd64" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-windowsltsc2022-amd64"
docker buildx imagetools create -t "${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64-alpine" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64-alpine" \
"${DOCKER_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm-alpine"
if [[ "${GITHUB_REF_NAME}" =~ ^release/.*$ ]]; then
docker buildx imagetools create -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-ppc64le" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-s390x"
docker buildx imagetools create -t "${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-alpine" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-amd64-alpine" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm64-alpine" \
"${EXTENSION_HUB_REPO}:${CONTAINER_IMAGE_TAG}-linux-arm-alpine"
fi

View File

@ -11,21 +11,31 @@ on:
- master - master
- develop - develop
- release/* - release/*
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs: jobs:
run-linters: run-linters:
name: Run linters name: Run linters
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '18' node-version: ${{ env.NODE_VERSION }}
cache: 'yarn' cache: 'yarn'
- uses: actions/setup-go@v4 - uses: actions/setup-go@v4
with: with:
go-version: 1.19.5 go-version: ${{ env.GO_VERSION }}
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile
- name: Run linters - name: Run linters
uses: wearerequired/lint-action@v1 uses: wearerequired/lint-action@v1
@ -41,6 +51,5 @@ jobs:
- name: GolangCI-Lint - name: GolangCI-Lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3
with: with:
version: v1.54.1 version: v1.55.2
working-directory: api
args: --timeout=10m -c .golangci.yaml args: --timeout=10m -c .golangci.yaml

View File

@ -5,6 +5,9 @@ on:
- cron: '0 20 * * *' - cron: '0 20 * * *'
workflow_dispatch: workflow_dispatch:
env:
GO_VERSION: 1.21.6
jobs: jobs:
client-dependencies: client-dependencies:
name: Client Dependency Check name: Client Dependency Check
@ -61,7 +64,7 @@ jobs:
- name: install Go - name: install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.19.5' go-version: ${{ env.GO_VERSION }}
- name: download Go modules - name: download Go modules
run: cd ./api && go get -t -v -d ./... run: cd ./api && go get -t -v -d ./...
@ -72,7 +75,7 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: | run: |
yarn global add snyk yarn global add snyk
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || : snyk test --file=./go.mod --json-file-output=snyk.json 2>/dev/null || :
- name: upload scan result as develop artifact - name: upload scan result as develop artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -102,7 +105,8 @@ jobs:
if: >- if: >-
github.ref == 'refs/heads/develop' github.ref == 'refs/heads/develop'
outputs: outputs:
image: ${{ steps.set-matrix.outputs.image_result }} image-trivy: ${{ steps.set-trivy-matrix.outputs.image_trivy_result }}
image-docker-scout: ${{ steps.set-docker-scout-matrix.outputs.image_docker_scout_result }}
steps: steps:
- name: scan vulnerabilities by Trivy - name: scan vulnerabilities by Trivy
uses: docker://docker.io/aquasec/trivy:latest uses: docker://docker.io/aquasec/trivy:latest
@ -110,27 +114,59 @@ jobs:
with: with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress portainerci/portainer:develop args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress portainerci/portainer:develop
- name: upload image security scan result as artifact - name: upload Trivy image security scan result as artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: image-security-scan-develop-result name: image-security-scan-develop-result
path: image-trivy.json path: image-trivy.json
- name: develop scan report export to html - name: develop Trivy scan report export to html
run: | run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=table --export --export-filename="/data/image-result") $(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=table --export --export-filename="/data/image-trivy-result")
- name: upload html file as artifact - name: upload html file as Trivy artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: html-image-result-${{github.run_id}} name: html-image-result-${{github.run_id}}
path: image-result.html path: image-trivy-result.html
- name: analyse vulnerabilities - name: analyse vulnerabilities from Trivy
id: set-matrix id: set-trivy-matrix
run: | run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=matrix) result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=trivy --path="/data/image-trivy.json" --output-type=matrix)
echo "image_result=${result}" >> $GITHUB_OUTPUT echo "image_trivy_result=${result}" >> $GITHUB_OUTPUT
- name: scan vulnerabilities by Docker Scout
uses: docker/scout-action@v1
continue-on-error: true
with:
command: cves
image: portainerci/portainer:develop
sarif-file: image-docker-scout.json
dockerhub-user: ${{ secrets.DOCKER_HUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: upload Docker Scout image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-develop-result
path: image-docker-scout.json
- name: develop Docker Scout scan report export to html
run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=docker-scout --path="/data/image-docker-scout.json" --output-type=table --export --export-filename="/data/image-docker-scout-result")
- name: upload html file as Docker Scout artifact
uses: actions/upload-artifact@v3
with:
name: html-image-result-${{github.run_id}}
path: image-docker-scout-result.html
- name: analyse vulnerabilities from Docker Scout
id: set-docker-scout-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest summary --report-type=docker-scout --path="/data/image-docker-scout.json" --output-type=matrix)
echo "image_docker_scout_result=${result}" >> $GITHUB_OUTPUT
result-analysis: result-analysis:
name: Analyse Scan Results name: Analyse Scan Results
@ -142,22 +178,26 @@ jobs:
matrix: matrix:
js: ${{fromJson(needs.client-dependencies.outputs.js)}} js: ${{fromJson(needs.client-dependencies.outputs.js)}}
go: ${{fromJson(needs.server-dependencies.outputs.go)}} go: ${{fromJson(needs.server-dependencies.outputs.go)}}
image: ${{fromJson(needs.image-vulnerability.outputs.image)}} image-trivy: ${{fromJson(needs.image-vulnerability.outputs.image-trivy)}}
image-docker-scout: ${{fromJson(needs.image-vulnerability.outputs.image-docker-scout)}}
steps: steps:
- name: display the results of js, Go, and image scan - name: display the results of js, Go, and image scan
run: | run: |
echo "${{ matrix.js.status }}" echo "${{ matrix.js.status }}"
echo "${{ matrix.go.status }}" echo "${{ matrix.go.status }}"
echo "${{ matrix.image.status }}" echo "${{ matrix.image-trivy.status }}"
echo "${{ matrix.image-docker-scout.status }}"
echo "${{ matrix.js.summary }}" echo "${{ matrix.js.summary }}"
echo "${{ matrix.go.summary }}" echo "${{ matrix.go.summary }}"
echo "${{ matrix.image.summary }}" echo "${{ matrix.image-trivy.summary }}"
echo "${{ matrix.image-docker-scout.summary }}"
- name: send message to Slack - name: send message to Slack
if: >- if: >-
matrix.js.status == 'failure' || matrix.js.status == 'failure' ||
matrix.go.status == 'failure' || matrix.go.status == 'failure' ||
matrix.image.status == 'failure' matrix.image-trivy.status == 'failure' ||
matrix.image-docker-scout.status == 'failure'
uses: slackapi/slack-github-action@v1.23.0 uses: slackapi/slack-github-action@v1.23.0
with: with:
payload: | payload: |
@ -193,7 +233,14 @@ jobs:
"type": "section", "type": "section",
"text": { "text": {
"type": "mrkdwn", "type": "mrkdwn",
"text": "*Image vulnerability check*: *${{ matrix.image.status }}*\n${{ matrix.image.summary }}\n" "text": "*Image Trivy vulnerability check*: *${{ matrix.image-trivy.status }}*\n${{ matrix.image-trivy.summary }}\n"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Image Docker Scout vulnerability check*: *${{ matrix.image-docker-scout.status }}*\n${{ matrix.image-docker-scout.summary }}\n"
} }
} }
] ]

View File

@ -7,20 +7,24 @@ on:
- edited - edited
paths: paths:
- 'package.json' - 'package.json'
- 'api/go.mod' - 'go.mod'
- 'gruntfile.js'
- 'build/linux/Dockerfile' - 'build/linux/Dockerfile'
- 'build/linux/alpine.Dockerfile' - 'build/linux/alpine.Dockerfile'
- 'build/windows/Dockerfile' - 'build/windows/Dockerfile'
- '.github/workflows/pr-security.yml' - '.github/workflows/pr-security.yml'
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs: jobs:
client-dependencies: client-dependencies:
name: Client Dependency Check name: Client Dependency Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request && github.event.pull_request &&
github.event.review.body == '/scan' github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
outputs: outputs:
jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }} jsdiff: ${{ steps.set-diff-matrix.outputs.js_diff_result }}
steps: steps:
@ -74,7 +78,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request && github.event.pull_request &&
github.event.review.body == '/scan' github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
outputs: outputs:
godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }} godiff: ${{ steps.set-diff-matrix.outputs.go_diff_result }}
steps: steps:
@ -84,7 +89,7 @@ jobs:
- name: install Go - name: install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.19.5' go-version: ${{ env.GO_VERSION }}
- name: download Go modules - name: download Go modules
run: cd ./api && go get -t -v -d ./... run: cd ./api && go get -t -v -d ./...
@ -95,7 +100,7 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: | run: |
yarn global add snyk yarn global add snyk
snyk test --file=./api/go.mod --json-file-output=snyk.json 2>/dev/null || : snyk test --file=./go.mod --json-file-output=snyk.json 2>/dev/null || :
- name: upload scan result as pull-request artifact - name: upload scan result as pull-request artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -136,22 +141,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request && github.event.pull_request &&
github.event.review.body == '/scan' github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
outputs: outputs:
imagediff: ${{ steps.set-diff-matrix.outputs.image_diff_result }} imagediff-trivy: ${{ steps.set-diff-trivy-matrix.outputs.image_diff_trivy_result }}
imagediff-docker-scout: ${{ steps.set-diff-docker-scout-matrix.outputs.image_diff_docker_scout_result }}
steps: steps:
- name: checkout code - name: checkout code
uses: actions/checkout@master uses: actions/checkout@master
- name: install Go 1.19.5 - name: install Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.19.5' go-version: ${{ env.GO_VERSION }}
- name: install Node.js 18.x - name: install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 18.x node-version: ${{ env.NODE_VERSION }}
- name: Install packages - name: Install packages
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
@ -167,26 +174,26 @@ jobs:
with: with:
context: . context: .
file: build/linux/Dockerfile file: build/linux/Dockerfile
tags: trivy-portainer:${{ github.sha }} tags: local-portainer:${{ github.sha }}
outputs: type=docker,dest=/tmp/trivy-portainer-image.tar outputs: type=docker,dest=/tmp/local-portainer-image.tar
- name: load docker image - name: load docker image
run: | run: |
docker load --input /tmp/trivy-portainer-image.tar docker load --input /tmp/local-portainer-image.tar
- name: scan vulnerabilities by Trivy - name: scan vulnerabilities by Trivy
uses: docker://docker.io/aquasec/trivy:latest uses: docker://docker.io/aquasec/trivy:latest
continue-on-error: true continue-on-error: true
with: with:
args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress trivy-portainer:${{ github.sha }} args: image --ignore-unfixed=true --vuln-type="os,library" --exit-code=1 --format="json" --output="image-trivy.json" --no-progress local-portainer:${{ github.sha }}
- name: upload image security scan result as artifact - name: upload Trivy image security scan result as artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: image-security-scan-feature-result name: image-security-scan-feature-result
path: image-trivy.json path: image-trivy.json
- name: download artifacts from develop branch built by nightly scan - name: download Trivy artifacts from develop branch built by nightly scan
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
@ -198,21 +205,65 @@ jobs:
echo "null" > ./image-trivy-develop.json echo "null" > ./image-trivy-develop.json
fi fi
- name: pr vs develop scan report comparison export to html - name: pr vs develop Trivy scan report comparison export to html
run: | run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=table --export --export-filename="/data/image-result") $(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=table --export --export-filename="/data/image-trivy-result")
- name: upload html file as artifact - name: upload html file as Trivy artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: html-image-result-compare-to-develop-${{github.run_id}} name: html-image-result-compare-to-develop-${{github.run_id}}
path: image-result.html path: image-trivy-result.html
- name: analyse different vulnerabilities against develop branch - name: analyse different vulnerabilities against develop branch by Trivy
id: set-diff-matrix id: set-diff-trivy-matrix
run: | run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=matrix) result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=trivy --path="/data/image-trivy-feature.json" --compare-to="/data/image-trivy-develop.json" --output-type=matrix)
echo "image_diff_result=${result}" >> $GITHUB_OUTPUT echo "image_diff_trivy_result=${result}" >> $GITHUB_OUTPUT
- name: scan vulnerabilities by Docker Scout
uses: docker/scout-action@v1
continue-on-error: true
with:
command: cves
image: local-portainer:${{ github.sha }}
sarif-file: image-docker-scout.json
dockerhub-user: ${{ secrets.DOCKER_HUB_USERNAME }}
dockerhub-password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: upload Docker Scout image security scan result as artifact
uses: actions/upload-artifact@v3
with:
name: image-security-scan-feature-result
path: image-docker-scout.json
- name: download Docker Scout artifacts from develop branch built by nightly scan
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mv ./image-docker-scout.json ./image-docker-scout-feature.json
(gh run download -n image-security-scan-develop-result -R ${{ github.repository }} 2>&1 >/dev/null) || :
if [[ -e ./image-docker-scout.json ]]; then
mv ./image-docker-scout.json ./image-docker-scout-develop.json
else
echo "null" > ./image-docker-scout-develop.json
fi
- name: pr vs develop Docker Scout scan report comparison export to html
run: |
$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=docker-scout --path="/data/image-docker-scout-feature.json" --compare-to="/data/image-docker-scout-develop.json" --output-type=table --export --export-filename="/data/image-docker-scout-result")
- name: upload html file as Docker Scout artifact
uses: actions/upload-artifact@v3
with:
name: html-image-result-compare-to-develop-${{github.run_id}}
path: image-docker-scout-result.html
- name: analyse different vulnerabilities against develop branch by Docker Scout
id: set-diff-docker-scout-matrix
run: |
result=$(docker run --rm -v ${{ github.workspace }}:/data portainerci/code-security-report:latest diff --report-type=docker-scout --path="/data/image-docker-scout-feature.json" --compare-to="/data/image-docker-scout-develop.json" --output-type=matrix)
echo "image_diff_docker_scout_result=${result}" >> $GITHUB_OUTPUT
result-analysis: result-analysis:
name: Analyse Scan Result Against develop Branch name: Analyse Scan Result Against develop Branch
@ -220,23 +271,28 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request && github.event.pull_request &&
github.event.review.body == '/scan' github.event.review.body == '/scan' &&
github.event.pull_request.draft == false
strategy: strategy:
matrix: matrix:
jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}} jsdiff: ${{fromJson(needs.client-dependencies.outputs.jsdiff)}}
godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}} godiff: ${{fromJson(needs.server-dependencies.outputs.godiff)}}
imagediff: ${{fromJson(needs.image-vulnerability.outputs.imagediff)}} imagediff-trivy: ${{fromJson(needs.image-vulnerability.outputs.imagediff-trivy)}}
imagediff-docker-scout: ${{fromJson(needs.image-vulnerability.outputs.imagediff-docker-scout)}}
steps: steps:
- name: check job status of diff result - name: check job status of diff result
if: >- if: >-
matrix.jsdiff.status == 'failure' || matrix.jsdiff.status == 'failure' ||
matrix.godiff.status == 'failure' || matrix.godiff.status == 'failure' ||
matrix.imagediff.status == 'failure' matrix.imagediff-trivy.status == 'failure' ||
matrix.imagediff-docker-scout.status == 'failure'
run: | run: |
echo "${{ matrix.jsdiff.status }}" echo "${{ matrix.jsdiff.status }}"
echo "${{ matrix.godiff.status }}" echo "${{ matrix.godiff.status }}"
echo "${{ matrix.imagediff.status }}" echo "${{ matrix.imagediff-trivy.status }}"
echo "${{ matrix.imagediff-docker-scout.status }}"
echo "${{ matrix.jsdiff.summary }}" echo "${{ matrix.jsdiff.summary }}"
echo "${{ matrix.godiff.summary }}" echo "${{ matrix.godiff.summary }}"
echo "${{ matrix.imagediff.summary }}" echo "${{ matrix.imagediff-trivy.summary }}"
echo "${{ matrix.imagediff-docker-scout.summary }}"
exit 1 exit 1

View File

@ -1,7 +1,8 @@
name: Close Stale Issues name: Close Stale Issues
on: on:
schedule: schedule:
- cron: '0 12 * * *' - cron: '0 12 * * *'
workflow_dispatch:
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -9,7 +10,7 @@ jobs:
issues: write issues: write
steps: steps:
- uses: actions/stale@v4.0.0 - uses: actions/stale@v8
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,25 +1,56 @@
name: Test name: Test
on: push
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
on:
pull_request:
branches:
- master
- develop
- release/*
types:
- opened
- reopened
- synchronize
- ready_for_review
push:
branches:
- master
- develop
- release/*
jobs: jobs:
test-client: test-client:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: with:
node-version: '18' node-version: ${{ env.NODE_VERSION }}
cache: 'yarn' cache: 'yarn'
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile
- name: Run tests - name: Run tests
run: yarn jest --maxWorkers=2 run: make test-client ARGS="--maxWorkers=2 --minWorkers=1"
test-server: test-server:
strategy:
matrix:
config:
- { platform: linux, arch: amd64 }
- { platform: linux, arch: arm64 }
- { platform: windows, arch: amd64, version: 1809 }
- { platform: windows, arch: amd64, version: ltsc2022 }
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: 1.19.5 go-version: ${{ env.GO_VERSION }}
- name: Run tests - name: Run tests
run: make test-server run: make test-server

View File

@ -6,22 +6,32 @@ on:
- master - master
- develop - develop
- 'release/*' - 'release/*'
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
GO_VERSION: 1.21.6
NODE_VERSION: 18.x
jobs: jobs:
openapi-spec: openapi-spec:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: '1.18' go-version: ${{ env.GO_VERSION }}
- name: Download golang modules - name: Download golang modules
run: cd ./api && go get -t -v -d ./... run: cd ./api && go get -t -v -d ./...
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '18' node-version: ${{ env.NODE_VERSION }}
cache: 'yarn' cache: 'yarn'
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile

32
.golangci.yaml Normal file
View File

@ -0,0 +1,32 @@
linters:
# Disable all linters, the defaults don't pass on our code yet
disable-all: true
# Enable these for now
enable:
- depguard
- govet
- errorlint
- exportloopref
linters-settings:
depguard:
rules:
main:
deny:
- pkg: 'github.com/sirupsen/logrus'
desc: 'logging is allowed only by github.com/rs/zerolog'
- pkg: 'golang.org/x/exp'
desc: 'exp is not allowed'
files:
- '!**/*_test.go'
- '!**/base.go'
- '!**/base_tx.go'
# errorlint is causing a typecheck error for some reason. The go compiler will report these
# anyway, so ignore them from the linter
issues:
exclude-rules:
- path: ./
linters:
- typecheck

View File

@ -102,8 +102,7 @@ lint-client: ## Lint client code
yarn lint yarn lint
lint-server: ## Lint server code lint-server: ## Lint server code
cd api && go vet ./... golangci-lint run --timeout=10m -c .golangci.yaml
##@ Extension ##@ Extension
.PHONY: dev-extension .PHONY: dev-extension
@ -124,3 +123,4 @@ docs-validate: docs-build ## Validate docs
.PHONY: help .PHONY: help
help: ## Display this help help: ## Display this help
@awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

View File

@ -944,6 +944,6 @@
} }
], ],
"version": { "version": {
"VERSION": "{\"SchemaVersion\":\"2.19.4\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}" "VERSION": "{\"SchemaVersion\":\"2.19.5\",\"MigratorCount\":0,\"Edition\":1,\"InstanceID\":\"463d5c47-0ea5-4aca-85b1-405ceefee254\"}"
} }
} }

View File

@ -74,7 +74,12 @@ func (handler *Handler) authenticate(rw http.ResponseWriter, r *http.Request) *h
if settings.AuthenticationMethod == portainer.AuthenticationInternal || if settings.AuthenticationMethod == portainer.AuthenticationInternal ||
settings.AuthenticationMethod == portainer.AuthenticationOAuth || settings.AuthenticationMethod == portainer.AuthenticationOAuth ||
(settings.AuthenticationMethod == portainer.AuthenticationLDAP && !settings.LDAPSettings.AutoCreateUsers) { (settings.AuthenticationMethod == portainer.AuthenticationLDAP && !settings.LDAPSettings.AutoCreateUsers) {
return &httperror.HandlerError{StatusCode: http.StatusUnprocessableEntity, Message: "Invalid credentials", Err: httperrors.ErrUnauthorized} // avoid username enumeration timing attack by creating a fake user
// https://en.wikipedia.org/wiki/Timing_attack
user = &portainer.User{
Username: "portainer-fake-username",
Password: "$2a$10$abcdefghijklmnopqrstuvwx..ABCDEFGHIJKLMNOPQRSTUVWXYZ12", // fake but valid format bcrypt hash
}
} }
} }
@ -111,7 +116,11 @@ func (handler *Handler) authenticateInternal(w http.ResponseWriter, user *portai
func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.User, username, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError { func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.User, username, password string, ldapSettings *portainer.LDAPSettings) *httperror.HandlerError {
err := handler.LDAPService.AuthenticateUser(username, password, ldapSettings) err := handler.LDAPService.AuthenticateUser(username, password, ldapSettings)
if err != nil { if err != nil {
return httperror.Forbidden("Only initial admin is allowed to login without oauth", err) if errors.Is(err, httperrors.ErrUnauthorized) {
return httperror.NewError(http.StatusUnprocessableEntity, "Invalid credentials", httperrors.ErrUnauthorized)
}
return httperror.InternalServerError("Unable to authenticate user against LDAP", err)
} }
if user == nil { if user == nil {

View File

@ -84,7 +84,7 @@ type Handler struct {
} }
// @title PortainerCE API // @title PortainerCE API
// @version 2.19.4 // @version 2.19.5
// @description.markdown api-description.md // @description.markdown api-description.md
// @termsOfService // @termsOfService

View File

@ -75,7 +75,14 @@ func (*Service) AuthenticateUser(username, password string, settings *portainer.
userDN, err := searchUser(username, connection, settings.SearchSettings) userDN, err := searchUser(username, connection, settings.SearchSettings)
if err != nil { if err != nil {
return err if errors.Is(err, errUserNotFound) {
// prevent user enumeration timing attack by attempting the bind with a fake user
// and whatever password was provided should definately fail
// https://en.wikipedia.org/wiki/Timing_attack
userDN = "portainer-fake-ldap-username"
} else {
return err
}
} }
err = connection.Bind(userDN, password) err = connection.Bind(userDN, password)

View File

@ -1561,7 +1561,7 @@ type (
const ( const (
// APIVersion is the version number of the Portainer API // APIVersion is the version number of the Portainer API
APIVersion = "2.19.4" APIVersion = "2.19.5"
// Edition is what this edition of Portainer is called // Edition is what this edition of Portainer is called
Edition = PortainerCE Edition = PortainerCE
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax // ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax

View File

@ -1,4 +1,4 @@
module github.com/portainer/portainer/api module github.com/portainer/portainer
go 1.20 go 1.20
@ -164,3 +164,4 @@ require (
// Remove below line when the "determinstic key" patch for Chisel merged // Remove below line when the "determinstic key" patch for Chisel merged
replace github.com/jpillora/chisel => github.com/portainer/chisel v0.0.0-20230704222304-426f515c6c25 replace github.com/jpillora/chisel => github.com/portainer/chisel v0.0.0-20230704222304-426f515c6c25

View File

@ -318,10 +318,6 @@ github.com/portainer/libhttp v0.0.0-20230615144939-a999f666d9a9 h1:Jq8g/pDcFL1Z/
github.com/portainer/libhttp v0.0.0-20230615144939-a999f666d9a9/go.mod h1:H49JLiywwLt2rrJVroafEWy8fIs0i7mThAThK40sbb8= github.com/portainer/libhttp v0.0.0-20230615144939-a999f666d9a9/go.mod h1:H49JLiywwLt2rrJVroafEWy8fIs0i7mThAThK40sbb8=
github.com/portainer/portainer/pkg/featureflags v0.0.0-20230711022654-64b227b2e146 h1:8JVXjyFhGgSogKGOddnOROZxBEeWNvvo3EPT9uIJ2Xo= github.com/portainer/portainer/pkg/featureflags v0.0.0-20230711022654-64b227b2e146 h1:8JVXjyFhGgSogKGOddnOROZxBEeWNvvo3EPT9uIJ2Xo=
github.com/portainer/portainer/pkg/featureflags v0.0.0-20230711022654-64b227b2e146/go.mod h1:x4Lpq/BjFhZmuNB8e8FO0ObRPQ/Z/V9rTe54bMedf1A= github.com/portainer/portainer/pkg/featureflags v0.0.0-20230711022654-64b227b2e146/go.mod h1:x4Lpq/BjFhZmuNB8e8FO0ObRPQ/Z/V9rTe54bMedf1A=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20230711022654-64b227b2e146 h1:1qW7quKyFG4tOnMcnnqyYsDVfL09etO1h/Cu/3ak7KU=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20230711022654-64b227b2e146/go.mod h1:cFRD6PvOwpd2pf/O1r/IMKl+ZB12pWfo/Evleh3aCfM=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20230919060741-8f42ba025479 h1:DbmhSQZpDo5f0cr+CKLJqoqhQiuxp8QFXdZsjPS1lI4=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20230919060741-8f42ba025479/go.mod h1:cFRD6PvOwpd2pf/O1r/IMKl+ZB12pWfo/Evleh3aCfM=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20230928223730-157393c965ce h1:DQTMXYH1zn2DzuAe+4rT40JqdHLhpHHJ2pzRFhvZ/+c= github.com/portainer/portainer/pkg/libhelm v0.0.0-20230928223730-157393c965ce h1:DQTMXYH1zn2DzuAe+4rT40JqdHLhpHHJ2pzRFhvZ/+c=
github.com/portainer/portainer/pkg/libhelm v0.0.0-20230928223730-157393c965ce/go.mod h1:cFRD6PvOwpd2pf/O1r/IMKl+ZB12pWfo/Evleh3aCfM= github.com/portainer/portainer/pkg/libhelm v0.0.0-20230928223730-157393c965ce/go.mod h1:cFRD6PvOwpd2pf/O1r/IMKl+ZB12pWfo/Evleh3aCfM=
github.com/portainer/portainer/pkg/libstack v0.0.0-20230711022654-64b227b2e146 h1:ZGj+j5HoajaO+mXgCm6NzOU+zUdIlJK2amagB+QIDvc= github.com/portainer/portainer/pkg/libstack v0.0.0-20230711022654-64b227b2e146 h1:ZGj+j5HoajaO+mXgCm6NzOU+zUdIlJK2amagB+QIDvc=

View File

@ -1,9 +0,0 @@
go 1.19
use (
./api
./pkg/featureflags
./pkg/libhelm
./pkg/libstack
./third_party/digest
)

View File

@ -1,11 +0,0 @@
#!/bin/bash
cd api
if golangci-lint run --timeout=10m -c .golangci.yaml
then
echo "golangci-lint run successfully"
else
echo "golangci-lint run failed"
exit 1
fi

View File

@ -2,5 +2,5 @@ module.exports = {
'*.(js|ts){,x}': 'eslint --cache --fix', '*.(js|ts){,x}': 'eslint --cache --fix',
'*.(ts){,x}': () => 'tsc --noEmit', '*.(ts){,x}': () => 'tsc --noEmit',
'*.{js,ts,tsx,css,md,html,json}': 'prettier --write', '*.{js,ts,tsx,css,md,html,json}': 'prettier --write',
'*.go': 'bash golangci-lint.sh', '*.go': () => 'make lint-server',
}; };

View File

@ -2,7 +2,7 @@
"author": "Portainer.io", "author": "Portainer.io",
"name": "portainer", "name": "portainer",
"homepage": "http://portainer.io", "homepage": "http://portainer.io",
"version": "2.19.4", "version": "2.19.5",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.com:portainer/portainer.git" "url": "git@github.com:portainer/portainer.git"