mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-28 21:25:55 +01:00
Merge branch 'main' into uffizzi-k8s
This commit is contained in:
commit
06199e415e
10
.github/workflows/CI.yml
vendored
10
.github/workflows/CI.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -105,7 +105,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -160,7 +160,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -215,7 +215,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@ -268,7 +268,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
2
.github/workflows/build-package.yml
vendored
2
.github/workflows/build-package.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- name: Setup Docker
|
||||
uses: docker-practice/actions-setup-docker@master
|
||||
|
2
.github/workflows/conformance_test.yml
vendored
2
.github/workflows/conformance_test.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Set up Go 1.21
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.21.3
|
||||
go-version: 1.21.4
|
||||
id: go
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
@ -164,6 +164,8 @@ Harbor backend is written in [Go](http://golang.org/). If you don't have a Harbo
|
||||
| 2.7 | 1.19.4 |
|
||||
| 2.8 | 1.20.6 |
|
||||
| 2.9 | 1.21.3 |
|
||||
| 2.10 | 1.21.4 |
|
||||
|
||||
|
||||
Ensure your GOPATH and PATH have been configured in accordance with the Go environment instructions.
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -140,7 +140,7 @@ GOINSTALL=$(GOCMD) install
|
||||
GOTEST=$(GOCMD) test
|
||||
GODEP=$(GOTEST) -i
|
||||
GOFMT=gofmt -w
|
||||
GOBUILDIMAGE=golang:1.21.3
|
||||
GOBUILDIMAGE=golang:1.21.4
|
||||
GOBUILDPATHINCONTAINER=/harbor
|
||||
|
||||
# go build
|
||||
|
@ -153,7 +153,7 @@ log:
|
||||
# port: 5140
|
||||
|
||||
#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY!
|
||||
_version: 2.9.0
|
||||
_version: 2.10.0
|
||||
|
||||
# Uncomment external_database if using external database.
|
||||
# external_database:
|
||||
|
@ -1 +1,3 @@
|
||||
DROP TABLE IF EXISTS harbor_resource_label;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_artifact_accessory_subject_artifact_id ON artifact_accessory (subject_artifact_id);
|
@ -10,7 +10,7 @@ from migrations import accept_versions
|
||||
@click.command()
|
||||
@click.option('-i', '--input', 'input_', required=True, help="The path of original config file")
|
||||
@click.option('-o', '--output', default='', help="the path of output config file")
|
||||
@click.option('-t', '--target', default='2.9.0', help="target version of input path")
|
||||
@click.option('-t', '--target', default='2.10.0', help="target version of input path")
|
||||
def migrate(input_, output, target):
|
||||
"""
|
||||
migrate command will migrate config file style to specific version
|
||||
|
@ -2,4 +2,4 @@ import os
|
||||
|
||||
MIGRATION_BASE_DIR = os.path.dirname(__file__)
|
||||
|
||||
accept_versions = {'1.9.0', '1.10.0', '2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0', '2.7.0', '2.8.0', '2.9.0'}
|
||||
accept_versions = {'1.9.0', '1.10.0', '2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0', '2.7.0', '2.8.0', '2.9.0','2.10.0'}
|
21
make/photon/prepare/migrations/version_2_10_0/__init__.py
Normal file
21
make/photon/prepare/migrations/version_2_10_0/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
import os
|
||||
from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoescape
|
||||
from utils.migration import read_conf
|
||||
|
||||
revision = '2.10.0'
|
||||
down_revisions = ['2.9.0']
|
||||
|
||||
def migrate(input_cfg, output_cfg):
|
||||
current_dir = os.path.dirname(__file__)
|
||||
tpl = Environment(
|
||||
loader=FileSystemLoader(current_dir),
|
||||
undefined=StrictUndefined,
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
autoescape = select_autoescape()
|
||||
).get_template('harbor.yml.jinja')
|
||||
|
||||
config_dict = read_conf(input_cfg)
|
||||
|
||||
with open(output_cfg, 'w') as f:
|
||||
f.write(tpl.render(**config_dict))
|
666
make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja
Normal file
666
make/photon/prepare/migrations/version_2_10_0/harbor.yml.jinja
Normal file
@ -0,0 +1,666 @@
|
||||
# Configuration file of Harbor
|
||||
|
||||
# The IP address or hostname to access admin UI and registry service.
|
||||
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
|
||||
hostname: {{ hostname }}
|
||||
|
||||
# http related config
|
||||
{% if http is defined %}
|
||||
http:
|
||||
# port for http, default is 80. If https enabled, this port will redirect to https port
|
||||
port: {{ http.port }}
|
||||
{% else %}
|
||||
# http:
|
||||
# # port for http, default is 80. If https enabled, this port will redirect to https port
|
||||
# port: 80
|
||||
{% endif %}
|
||||
|
||||
{% if https is defined %}
|
||||
# https related config
|
||||
https:
|
||||
# https port for harbor, default is 443
|
||||
port: {{ https.port }}
|
||||
# The path of cert and key files for nginx
|
||||
certificate: {{ https.certificate }}
|
||||
private_key: {{ https.private_key }}
|
||||
{% else %}
|
||||
# https related config
|
||||
# https:
|
||||
# # https port for harbor, default is 443
|
||||
# port: 443
|
||||
# # The path of cert and key files for nginx
|
||||
# certificate: /your/certificate/path
|
||||
# private_key: /your/private/key/path
|
||||
{% endif %}
|
||||
|
||||
{% if internal_tls is defined %}
|
||||
# Uncomment following will enable tls communication between all harbor components
|
||||
internal_tls:
|
||||
# set enabled to true means internal tls is enabled
|
||||
enabled: {{ internal_tls.enabled | lower }}
|
||||
# put your cert and key files on dir
|
||||
dir: {{ internal_tls.dir }}
|
||||
# enable strong ssl ciphers (default: false)
|
||||
{% if internal_tls.strong_ssl_ciphers is defined %}
|
||||
strong_ssl_ciphers: {{ internal_tls.strong_ssl_ciphers | lower }}
|
||||
{% else %}
|
||||
strong_ssl_ciphers: false
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# internal_tls:
|
||||
# # set enabled to true means internal tls is enabled
|
||||
# enabled: true
|
||||
# # put your cert and key files on dir
|
||||
# dir: /etc/harbor/tls/internal
|
||||
# # enable strong ssl ciphers (default: false)
|
||||
# strong_ssl_ciphers: false
|
||||
{% endif %}
|
||||
|
||||
# Uncomment external_url if you want to enable external proxy
|
||||
# And when it enabled the hostname will no longer used
|
||||
{% if external_url is defined %}
|
||||
external_url: {{ external_url }}
|
||||
{% else %}
|
||||
# external_url: https://reg.mydomain.com:8433
|
||||
{% endif %}
|
||||
|
||||
# The initial password of Harbor admin
|
||||
# It only works in first time to install harbor
|
||||
# Remember Change the admin password from UI after launching Harbor.
|
||||
{% if harbor_admin_password is defined %}
|
||||
harbor_admin_password: {{ harbor_admin_password }}
|
||||
{% else %}
|
||||
harbor_admin_password: Harbor12345
|
||||
{% endif %}
|
||||
|
||||
# Harbor DB configuration
|
||||
database:
|
||||
{% if database is defined %}
|
||||
# The password for the root user of Harbor DB. Change this before any production use.
|
||||
password: {{ database.password}}
|
||||
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
|
||||
max_idle_conns: {{ database.max_idle_conns }}
|
||||
# The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
|
||||
# Note: the default number of connections is 1024 for postgres of harbor.
|
||||
max_open_conns: {{ database.max_open_conns }}
|
||||
{% else %}
|
||||
# The password for the root user of Harbor DB. Change this before any production use.
|
||||
password: root123
|
||||
# The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained.
|
||||
max_idle_conns: 100
|
||||
# The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections.
|
||||
# Note: the default number of connections is 1024 for postgres of harbor.
|
||||
max_open_conns: 900
|
||||
{% endif %}
|
||||
|
||||
{% if data_volume is defined %}
|
||||
# The default data volume
|
||||
data_volume: {{ data_volume }}
|
||||
{% else %}
|
||||
# The default data volume
|
||||
data_volume: /data
|
||||
{% endif %}
|
||||
|
||||
# Harbor Storage settings by default is using /data dir on local filesystem
|
||||
# Uncomment storage_service setting If you want to using external storage
|
||||
{% if storage_service is defined %}
|
||||
storage_service:
|
||||
{% for key, value in storage_service.items() %}
|
||||
{% if key == 'ca_bundle' %}
|
||||
# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore
|
||||
# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate.
|
||||
ca_bundle: {{ value if value is not none else '' }}
|
||||
{% elif key == 'redirect' %}
|
||||
# # set disable to true when you want to disable registry redirect
|
||||
redirect:
|
||||
{% if storage_service.redirect.disabled is defined %}
|
||||
disable: {{ storage_service.redirect.disabled | lower}}
|
||||
{% else %}
|
||||
disable: {{ storage_service.redirect.disable | lower}}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss
|
||||
# # for more info about this configuration please refer https://docs.docker.com/registry/configuration/
|
||||
{{ key }}:
|
||||
{% for k, v in value.items() %}
|
||||
{{ k }}: {{ v if v is not none else '' }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
# Harbor Storage settings by default is using /data dir on local filesystem
|
||||
# Uncomment storage_service setting If you want to using external storage
|
||||
# storage_service:
|
||||
# # ca_bundle is the path to the custom root ca certificate, which will be injected into the truststore
|
||||
# # of registry's and chart repository's containers. This is usually needed when the user hosts a internal storage with self signed certificate.
|
||||
# ca_bundle:
|
||||
|
||||
# # storage backend, default is filesystem, options include filesystem, azure, gcs, s3, swift and oss
|
||||
# # for more info about this configuration please refer https://docs.docker.com/registry/configuration/
|
||||
# filesystem:
|
||||
# maxthreads: 100
|
||||
# # set disable to true when you want to disable registry redirect
|
||||
# redirect:
|
||||
# disable: false
|
||||
{% endif %}
|
||||
|
||||
# Trivy configuration
|
||||
#
|
||||
# Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases.
|
||||
# It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached
|
||||
# in the local file system. In addition, the database contains the update timestamp so Trivy can detect whether it
|
||||
# should download a newer version from the Internet or use the cached one. Currently, the database is updated every
|
||||
# 12 hours and published as a new release to GitHub.
|
||||
{% if trivy is defined %}
|
||||
trivy:
|
||||
# ignoreUnfixed The flag to display only fixed vulnerabilities
|
||||
{% if trivy.ignore_unfixed is defined %}
|
||||
ignore_unfixed: {{ trivy.ignore_unfixed | lower }}
|
||||
{% else %}
|
||||
ignore_unfixed: false
|
||||
{% endif %}
|
||||
# timeout The duration to wait for scan completion
|
||||
{% if trivy.timeout is defined %}
|
||||
timeout: {{ trivy.timeout }}
|
||||
{% else %}
|
||||
timeout: 5m0s
|
||||
{% endif %}
|
||||
# skipUpdate The flag to enable or disable Trivy DB downloads from GitHub
|
||||
#
|
||||
# You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues.
|
||||
# If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and
|
||||
# `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path.
|
||||
{% if trivy.skip_update is defined %}
|
||||
skip_update: {{ trivy.skip_update | lower }}
|
||||
{% else %}
|
||||
skip_update: false
|
||||
{% endif %}
|
||||
#
|
||||
{% if trivy.offline_scan is defined %}
|
||||
offline_scan: {{ trivy.offline_scan | lower }}
|
||||
{% else %}
|
||||
offline_scan: false
|
||||
{% endif %}
|
||||
#
|
||||
# Comma-separated list of what security issues to detect. Possible values are `vuln`, `config` and `secret`. Defaults to `vuln`.
|
||||
{% if trivy.security_check is defined %}
|
||||
security_check: {{ trivy.security_check }}
|
||||
{% else %}
|
||||
security_check: vuln
|
||||
{% endif %}
|
||||
#
|
||||
# insecure The flag to skip verifying registry certificate
|
||||
{% if trivy.insecure is defined %}
|
||||
insecure: {{ trivy.insecure | lower }}
|
||||
{% else %}
|
||||
insecure: false
|
||||
{% endif %}
|
||||
# github_token The GitHub access token to download Trivy DB
|
||||
#
|
||||
# Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
|
||||
# for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
|
||||
# requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
|
||||
# https://developer.github.com/v3/#rate-limiting
|
||||
#
|
||||
# You can create a GitHub token by following the instructions in
|
||||
# https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
|
||||
#
|
||||
{% if trivy.github_token is defined %}
|
||||
github_token: {{ trivy.github_token }}
|
||||
{% else %}
|
||||
# github_token: xxx
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# trivy:
|
||||
# # ignoreUnfixed The flag to display only fixed vulnerabilities
|
||||
# ignore_unfixed: false
|
||||
# # skipUpdate The flag to enable or disable Trivy DB downloads from GitHub
|
||||
# #
|
||||
# # You might want to enable this flag in test or CI/CD environments to avoid GitHub rate limiting issues.
|
||||
# # If the flag is enabled you have to download the `trivy-offline.tar.gz` archive manually, extract `trivy.db` and
|
||||
# # `metadata.json` files and mount them in the `/home/scanner/.cache/trivy/db` path.
|
||||
# skip_update: false
|
||||
# #
|
||||
# #The offline_scan option prevents Trivy from sending API requests to identify dependencies.
|
||||
# # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it.
|
||||
# # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't
|
||||
# # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode.
|
||||
# # It would work if all the dependencies are in local.
|
||||
# # This option doesn’t affect DB download. You need to specify "skip-update" as well as "offline-scan" in an air-gapped environment.
|
||||
# offline_scan: false
|
||||
# #
|
||||
# # insecure The flag to skip verifying registry certificate
|
||||
# insecure: false
|
||||
# # github_token The GitHub access token to download Trivy DB
|
||||
# #
|
||||
# # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough
|
||||
# # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000
|
||||
# # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult
|
||||
# # https://developer.github.com/v3/#rate-limiting
|
||||
# #
|
||||
# # You can create a GitHub token by following the instructions in
|
||||
# # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
|
||||
# #
|
||||
# # github_token: xxx
|
||||
{% endif %}
|
||||
|
||||
jobservice:
|
||||
# Maximum number of job workers in job service
|
||||
{% if jobservice is defined %}
|
||||
max_job_workers: {{ jobservice.max_job_workers }}
|
||||
# The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB"
|
||||
{% if jobservice.job_loggers is defined %}
|
||||
job_loggers:
|
||||
{% for job_logger in jobservice.job_loggers %}
|
||||
- {{job_logger}}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
job_loggers:
|
||||
- STD_OUTPUT
|
||||
- FILE
|
||||
# - DB
|
||||
{% endif %}
|
||||
# The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
|
||||
{% if jobservice.logger_sweeper_duration is defined %}
|
||||
logger_sweeper_duration: {{ jobservice.logger_sweeper_duration }}
|
||||
{% else %}
|
||||
logger_sweeper_duration: 1
|
||||
{% endif %}
|
||||
{% else %}
|
||||
max_job_workers: 10
|
||||
# The jobLoggers backend name, only support "STD_OUTPUT", "FILE" and/or "DB"
|
||||
job_loggers:
|
||||
- STD_OUTPUT
|
||||
- FILE
|
||||
# - DB
|
||||
# The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`)
|
||||
logger_sweeper_duration: 1
|
||||
{% endif %}
|
||||
|
||||
notification:
|
||||
# Maximum retry count for webhook job
|
||||
{% if notification is defined %}
|
||||
webhook_job_max_retry: {{ notification.webhook_job_max_retry}}
|
||||
# HTTP client timeout for webhook job
|
||||
{% if notification.webhook_job_http_client_timeout is defined %}
|
||||
webhook_job_http_client_timeout: {{ notification.webhook_job_http_client_timeout }}
|
||||
{% else %}
|
||||
webhook_job_http_client_timeout: 3 #seconds
|
||||
{% endif %}
|
||||
{% else %}
|
||||
webhook_job_max_retry: 3
|
||||
# HTTP client timeout for webhook job
|
||||
webhook_job_http_client_timeout: 3 #seconds
|
||||
{% endif %}
|
||||
|
||||
# Log configurations
|
||||
log:
|
||||
# options are debug, info, warning, error, fatal
|
||||
{% if log is defined %}
|
||||
level: {{ log.level }}
|
||||
# configs for logs in local storage
|
||||
local:
|
||||
# Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated.
|
||||
rotate_count: {{ log.local.rotate_count }}
|
||||
# Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes.
|
||||
# If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G
|
||||
# are all valid.
|
||||
rotate_size: {{ log.local.rotate_size }}
|
||||
# The directory on your host that store log
|
||||
location: {{ log.local.location }}
|
||||
{% if log.external_endpoint is defined %}
|
||||
external_endpoint:
|
||||
# protocol used to transmit log to external endpoint, options is tcp or udp
|
||||
protocol: {{ log.external_endpoint.protocol }}
|
||||
# The host of external endpoint
|
||||
host: {{ log.external_endpoint.host }}
|
||||
# Port of external endpoint
|
||||
port: {{ log.external_endpoint.port }}
|
||||
{% else %}
|
||||
# Uncomment following lines to enable external syslog endpoint.
|
||||
# external_endpoint:
|
||||
# # protocol used to transmit log to external endpoint, options is tcp or udp
|
||||
# protocol: tcp
|
||||
# # The host of external endpoint
|
||||
# host: localhost
|
||||
# # Port of external endpoint
|
||||
# port: 5140
|
||||
{% endif %}
|
||||
{% else %}
|
||||
level: info
|
||||
# configs for logs in local storage
|
||||
local:
|
||||
# Log files are rotated log_rotate_count times before being removed. If count is 0, old versions are removed rather than rotated.
|
||||
rotate_count: 50
|
||||
# Log files are rotated only if they grow bigger than log_rotate_size bytes. If size is followed by k, the size is assumed to be in kilobytes.
|
||||
# If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. So size 100, size 100k, size 100M and size 100G
|
||||
# are all valid.
|
||||
rotate_size: 200M
|
||||
# The directory on your host that store log
|
||||
location: /var/log/harbor
|
||||
|
||||
# Uncomment following lines to enable external syslog endpoint.
|
||||
# external_endpoint:
|
||||
# # protocol used to transmit log to external endpoint, options is tcp or udp
|
||||
# protocol: tcp
|
||||
# # The host of external endpoint
|
||||
# host: localhost
|
||||
# # Port of external endpoint
|
||||
# port: 5140
|
||||
{% endif %}
|
||||
|
||||
|
||||
#This attribute is for migrator to detect the version of the .cfg file, DO NOT MODIFY!
|
||||
_version: 2.9.0
|
||||
{% if external_database is defined %}
|
||||
# Uncomment external_database if using external database.
|
||||
external_database:
|
||||
harbor:
|
||||
host: {{ external_database.harbor.host }}
|
||||
port: {{ external_database.harbor.port }}
|
||||
db_name: {{ external_database.harbor.db_name }}
|
||||
username: {{ external_database.harbor.username }}
|
||||
password: {{ external_database.harbor.password }}
|
||||
ssl_mode: {{ external_database.harbor.ssl_mode }}
|
||||
max_idle_conns: {{ external_database.harbor.max_idle_conns}}
|
||||
max_open_conns: {{ external_database.harbor.max_open_conns}}
|
||||
{% else %}
|
||||
# Uncomment external_database if using external database.
|
||||
# external_database:
|
||||
# harbor:
|
||||
# host: harbor_db_host
|
||||
# port: harbor_db_port
|
||||
# db_name: harbor_db_name
|
||||
# username: harbor_db_username
|
||||
# password: harbor_db_password
|
||||
# ssl_mode: disable
|
||||
# max_idle_conns: 2
|
||||
# max_open_conns: 0
|
||||
{% endif %}
|
||||
|
||||
{% if redis is defined %}
|
||||
redis:
|
||||
# # db_index 0 is for core, it's unchangeable
|
||||
{% if redis.registry_db_index is defined %}
|
||||
registry_db_index: {{ redis.registry_db_index }}
|
||||
{% else %}
|
||||
# # registry_db_index: 1
|
||||
{% endif %}
|
||||
{% if redis.jobservice_db_index is defined %}
|
||||
jobservice_db_index: {{ redis.jobservice_db_index }}
|
||||
{% else %}
|
||||
# # jobservice_db_index: 2
|
||||
{% endif %}
|
||||
{% if redis.trivy_db_index is defined %}
|
||||
trivy_db_index: {{ redis.trivy_db_index }}
|
||||
{% else %}
|
||||
# # trivy_db_index: 5
|
||||
{% endif %}
|
||||
{% if redis.harbor_db_index is defined %}
|
||||
harbor_db_index: {{ redis.harbor_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
{% endif %}
|
||||
{% if redis.cache_layer_db_index is defined %}
|
||||
cache_layer_db_index: {{ redis.cache_layer_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# Uncomment redis if need to customize redis db
|
||||
# redis:
|
||||
# # db_index 0 is for core, it's unchangeable
|
||||
# # registry_db_index: 1
|
||||
# # jobservice_db_index: 2
|
||||
# # trivy_db_index: 5
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
|
||||
{% if external_redis is defined %}
|
||||
external_redis:
|
||||
# support redis, redis+sentinel
|
||||
# host for redis: <host_redis>:<port_redis>
|
||||
# host for redis+sentinel:
|
||||
# <host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
|
||||
host: {{ external_redis.host }}
|
||||
password: {{ external_redis.password }}
|
||||
# Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH <username> <password> form.
|
||||
{% if external_redis.username is defined %}
|
||||
username: {{ external_redis.username }}
|
||||
{% else %}
|
||||
# username:
|
||||
{% endif %}
|
||||
# sentinel_master_set must be set to support redis+sentinel
|
||||
#sentinel_master_set:
|
||||
# db_index 0 is for core, it's unchangeable
|
||||
registry_db_index: {{ external_redis.registry_db_index }}
|
||||
jobservice_db_index: {{ external_redis.jobservice_db_index }}
|
||||
trivy_db_index: 5
|
||||
idle_timeout_seconds: 30
|
||||
{% if external_redis.harbor_db_index is defined %}
|
||||
harbor_db_index: {{ redis.harbor_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
{% endif %}
|
||||
{% if external_redis.cache_layer_db_index is defined %}
|
||||
cache_layer_db_index: {{ redis.cache_layer_db_index }}
|
||||
{% else %}
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
{% else %}
|
||||
# Umcomments external_redis if using external Redis server
|
||||
# external_redis:
|
||||
# # support redis, redis+sentinel
|
||||
# # host for redis: <host_redis>:<port_redis>
|
||||
# # host for redis+sentinel:
|
||||
# # <host_sentinel1>:<port_sentinel1>,<host_sentinel2>:<port_sentinel2>,<host_sentinel3>:<port_sentinel3>
|
||||
# host: redis:6379
|
||||
# password:
|
||||
# # Redis AUTH command was extended in Redis 6, it is possible to use it in the two-arguments AUTH <username> <password> form.
|
||||
# # username:
|
||||
# # sentinel_master_set must be set to support redis+sentinel
|
||||
# #sentinel_master_set:
|
||||
# # db_index 0 is for core, it's unchangeable
|
||||
# registry_db_index: 1
|
||||
# jobservice_db_index: 2
|
||||
# trivy_db_index: 5
|
||||
# idle_timeout_seconds: 30
|
||||
# # it's optional, the db for harbor business misc, by default is 0, uncomment it if you want to change it.
|
||||
# # harbor_db_index: 6
|
||||
# # it's optional, the db for harbor cache layer, by default is 0, uncomment it if you want to change it.
|
||||
# # cache_layer_db_index: 7
|
||||
{% endif %}
|
||||
|
||||
{% if uaa is defined %}
|
||||
# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert.
|
||||
uaa:
|
||||
ca_file: {{ uaa.ca_file }}
|
||||
{% else %}
|
||||
# Uncomment uaa for trusting the certificate of uaa instance that is hosted via self-signed cert.
|
||||
# uaa:
|
||||
# ca_file: /path/to/ca
|
||||
{% endif %}
|
||||
|
||||
|
||||
# Global proxy
|
||||
# Config http proxy for components, e.g. http://my.proxy.com:3128
|
||||
# Components doesn't need to connect to each others via http proxy.
|
||||
# Remove component from `components` array if want disable proxy
|
||||
# for it. If you want use proxy for replication, MUST enable proxy
|
||||
# for core and jobservice, and set `http_proxy` and `https_proxy`.
|
||||
# Add domain to the `no_proxy` field, when you want disable proxy
|
||||
# for some special registry.
|
||||
{% if proxy is defined %}
|
||||
proxy:
|
||||
http_proxy: {{ proxy.http_proxy or ''}}
|
||||
https_proxy: {{ proxy.https_proxy or ''}}
|
||||
no_proxy: {{ proxy.no_proxy or ''}}
|
||||
{% if proxy.components is defined %}
|
||||
components:
|
||||
{% for component in proxy.components %}
|
||||
{% if component != 'clair' %}
|
||||
- {{component}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
proxy:
|
||||
http_proxy:
|
||||
https_proxy:
|
||||
no_proxy:
|
||||
components:
|
||||
- core
|
||||
- jobservice
|
||||
- trivy
|
||||
{% endif %}
|
||||
|
||||
{% if metric is defined %}
|
||||
metric:
|
||||
enabled: {{ metric.enabled }}
|
||||
port: {{ metric.port }}
|
||||
path: {{ metric.path }}
|
||||
{% else %}
|
||||
# metric:
|
||||
# enabled: false
|
||||
# port: 9090
|
||||
# path: /metric
|
||||
{% endif %}
|
||||
|
||||
# Trace related config
|
||||
# only can enable one trace provider(jaeger or otel) at the same time,
|
||||
# and when using jaeger as provider, can only enable it with agent mode or collector mode.
|
||||
# if using jaeger collector mode, uncomment endpoint and uncomment username, password if needed
|
||||
# if using jaeger agetn mode uncomment agent_host and agent_port
|
||||
{% if trace is defined %}
|
||||
trace:
|
||||
enabled: {{ trace.enabled | lower}}
|
||||
sample_rate: {{ trace.sample_rate }}
|
||||
# # namespace used to diferenciate different harbor services
|
||||
{% if trace.namespace is defined %}
|
||||
namespace: {{ trace.namespace }}
|
||||
{% else %}
|
||||
# namespace:
|
||||
{% endif %}
|
||||
# # attributes is a key value dict contains user defined attributes used to initialize trace provider
|
||||
{% if trace.attributes is defined%}
|
||||
attributes:
|
||||
{% for name, value in trace.attributes.items() %}
|
||||
{{name}}: {{value}}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
# attributes:
|
||||
# application: harbor
|
||||
{% endif %}
|
||||
{% if trace.jaeger is defined%}
|
||||
jaeger:
|
||||
endpoint: {{trace.jaeger.endpoint or '' }}
|
||||
username: {{trace.jaeger.username or ''}}
|
||||
password: {{trace.jaeger.password or ''}}
|
||||
agent_host: {{trace.jaeger.agent_host or ''}}
|
||||
agent_port: {{trace.jaeger.agent_port or ''}}
|
||||
{% else %}
|
||||
# jaeger:
|
||||
# endpoint:
|
||||
# username:
|
||||
# password:
|
||||
# agent_host:
|
||||
# agent_port:
|
||||
{% endif %}
|
||||
{% if trace. otel is defined %}
|
||||
otel:
|
||||
endpoint: {{trace.otel.endpoint or '' }}
|
||||
url_path: {{trace.otel.url_path or '' }}
|
||||
compression: {{trace.otel.compression | lower }}
|
||||
insecure: {{trace.otel.insecure | lower }}
|
||||
timeout: {{trace.otel.timeout or '' }}
|
||||
{% else %}
|
||||
# otel:
|
||||
# endpoint: hostname:4318
|
||||
# url_path: /v1/traces
|
||||
# compression: false
|
||||
# insecure: true
|
||||
# # timeout is in seconds
|
||||
# timeout: 10
|
||||
{% endif%}
|
||||
{% else %}
|
||||
# trace:
|
||||
# enabled: true
|
||||
# # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth
|
||||
# sample_rate: 1
|
||||
# # # namespace used to differenciate different harbor services
|
||||
# # namespace:
|
||||
# # # attributes is a key value dict contains user defined attributes used to initialize trace provider
|
||||
# # attributes:
|
||||
# # application: harbor
|
||||
# # jaeger:
|
||||
# # endpoint: http://hostname:14268/api/traces
|
||||
# # username:
|
||||
# # password:
|
||||
# # agent_host: hostname
|
||||
# # agent_port: 6832
|
||||
# # otel:
|
||||
# # endpoint: hostname:4318
|
||||
# # url_path: /v1/traces
|
||||
# # compression: false
|
||||
# # insecure: true
|
||||
# # # timeout is in seconds
|
||||
# # timeout: 10
|
||||
{% endif %}
|
||||
|
||||
# enable purge _upload directories
|
||||
{% if upload_purging is defined %}
|
||||
upload_purging:
|
||||
enabled: {{ upload_purging.enabled | lower}}
|
||||
age: {{ upload_purging.age }}
|
||||
interval: {{ upload_purging.interval }}
|
||||
dryrun: {{ upload_purging.dryrun | lower}}
|
||||
{% else %}
|
||||
upload_purging:
|
||||
enabled: true
|
||||
# remove files in _upload directories which exist for a period of time, default is one week.
|
||||
age: 168h
|
||||
# the interval of the purge operations
|
||||
interval: 24h
|
||||
dryrun: false
|
||||
{% endif %}
|
||||
|
||||
# Cache related config
|
||||
{% if cache is defined %}
|
||||
cache:
|
||||
enabled: {{ cache.enabled | lower}}
|
||||
expire_hours: {{ cache.expire_hours }}
|
||||
{% else %}
|
||||
cache:
|
||||
enabled: false
|
||||
expire_hours: 24
|
||||
{% endif %}
|
||||
|
||||
# Harbor core configurations
|
||||
{% if core is defined %}
|
||||
core:
|
||||
# The provider for updating project quota(usage), there are 2 options, redis or db,
|
||||
# by default is implemented by db but you can switch the updation via redis which
|
||||
# can improve the performance of high concurrent pushing to the same project,
|
||||
# and reduce the database connections spike and occupies.
|
||||
# By redis will bring up some delay for quota usage updation for display, so only
|
||||
# suggest switch provider to redis if you were ran into the db connections spike aroud
|
||||
# the scenario of high concurrent pushing to same project, no improvment for other scenes.
|
||||
quota_update_provider: {{ core.quota_update_provider }}
|
||||
{% else %}
|
||||
# core:
|
||||
# # The provider for updating project quota(usage), there are 2 options, redis or db,
|
||||
# # by default is implemented by db but you can switch the updation via redis which
|
||||
# # can improve the performance of high concurrent pushing to the same project,
|
||||
# # and reduce the database connections spike and occupies.
|
||||
# # By redis will bring up some delay for quota usage updation for display, so only
|
||||
# # suggest switch provider to redis if you were ran into the db connections spike aroud
|
||||
# # the scenario of high concurrent pushing to same project, no improvment for other scenes.
|
||||
# quota_update_provider: redis # Or db
|
||||
{% endif %}
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.21.3
|
||||
FROM golang:1.21.4
|
||||
|
||||
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
|
||||
ENV BUILDTAGS include_oss include_gcs
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM golang:1.21.3
|
||||
FROM golang:1.21.4
|
||||
|
||||
ADD . /go/src/github.com/aquasecurity/harbor-scanner-trivy/
|
||||
WORKDIR /go/src/github.com/aquasecurity/harbor-scanner-trivy/
|
||||
|
@ -19,7 +19,7 @@ TEMP=$(mktemp -d ${TMPDIR-/tmp}/trivy-adapter.XXXXXX)
|
||||
git clone https://github.com/aquasecurity/harbor-scanner-trivy.git $TEMP
|
||||
cd $TEMP; git checkout $VERSION; cd -
|
||||
|
||||
echo "Building Trivy adapter binary based on golang:1.21.3..."
|
||||
echo "Building Trivy adapter binary based on golang:1.21.4..."
|
||||
cp Dockerfile.binary $TEMP
|
||||
docker build -f $TEMP/Dockerfile.binary -t trivy-adapter-golang $TEMP
|
||||
|
||||
|
@ -184,6 +184,7 @@ const (
|
||||
TraceOtelTimeout = "trace_otel_timeout"
|
||||
|
||||
GDPRDeleteUser = "gdpr_delete_user"
|
||||
GDPRAuditLogs = "gdpr_audit_logs"
|
||||
|
||||
// These variables are temporary solution for issue: https://github.com/goharbor/harbor/issues/16039
|
||||
// When user disable the pull count/time/audit log, it will decrease the database access, especially in large concurrency pull scenarios.
|
||||
@ -230,4 +231,14 @@ const (
|
||||
QuotaUpdateProvider = "quota_update_provider"
|
||||
// IllegalCharsInUsername is the illegal chars in username
|
||||
IllegalCharsInUsername = `,"~#%$`
|
||||
|
||||
// Beego web config
|
||||
// BeegoMaxMemoryBytes is the max memory(bytes) of the beego web config
|
||||
BeegoMaxMemoryBytes = "beego_max_memory_bytes"
|
||||
// DefaultBeegoMaxMemoryBytes sets default max memory to 128GB
|
||||
DefaultBeegoMaxMemoryBytes = 1 << 37
|
||||
// BeegoMaxUploadSizeBytes is the max upload size(bytes) of the beego web config
|
||||
BeegoMaxUploadSizeBytes = "beego_max_upload_size_bytes"
|
||||
// DefaultBeegoMaxUploadSizeBytes sets default max upload size to 128GB
|
||||
DefaultBeegoMaxUploadSizeBytes = 1 << 37
|
||||
)
|
||||
|
@ -85,11 +85,11 @@ var (
|
||||
"System": {
|
||||
{Resource: ResourceAuditLog, Action: ActionList},
|
||||
|
||||
{Resource: ResourcePreatPolicy, Action: ActionRead},
|
||||
{Resource: ResourcePreatPolicy, Action: ActionCreate},
|
||||
{Resource: ResourcePreatPolicy, Action: ActionDelete},
|
||||
{Resource: ResourcePreatPolicy, Action: ActionList},
|
||||
{Resource: ResourcePreatPolicy, Action: ActionUpdate},
|
||||
{Resource: ResourcePreatInstance, Action: ActionRead},
|
||||
{Resource: ResourcePreatInstance, Action: ActionCreate},
|
||||
{Resource: ResourcePreatInstance, Action: ActionDelete},
|
||||
{Resource: ResourcePreatInstance, Action: ActionList},
|
||||
{Resource: ResourcePreatInstance, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceProject, Action: ActionList},
|
||||
{Resource: ResourceProject, Action: ActionCreate},
|
||||
@ -102,9 +102,7 @@ var (
|
||||
|
||||
{Resource: ResourceReplication, Action: ActionRead},
|
||||
{Resource: ResourceReplication, Action: ActionCreate},
|
||||
{Resource: ResourceReplication, Action: ActionDelete},
|
||||
{Resource: ResourceReplication, Action: ActionList},
|
||||
{Resource: ResourceReplication, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceReplicationAdapter, Action: ActionList},
|
||||
|
||||
@ -123,14 +121,12 @@ var (
|
||||
|
||||
{Resource: ResourceGarbageCollection, Action: ActionRead},
|
||||
{Resource: ResourceGarbageCollection, Action: ActionCreate},
|
||||
{Resource: ResourceGarbageCollection, Action: ActionDelete},
|
||||
{Resource: ResourceGarbageCollection, Action: ActionList},
|
||||
{Resource: ResourceGarbageCollection, Action: ActionUpdate},
|
||||
{Resource: ResourceGarbageCollection, Action: ActionStop},
|
||||
|
||||
{Resource: ResourcePurgeAuditLog, Action: ActionRead},
|
||||
{Resource: ResourcePurgeAuditLog, Action: ActionCreate},
|
||||
{Resource: ResourcePurgeAuditLog, Action: ActionDelete},
|
||||
{Resource: ResourcePurgeAuditLog, Action: ActionList},
|
||||
{Resource: ResourcePurgeAuditLog, Action: ActionUpdate},
|
||||
{Resource: ResourcePurgeAuditLog, Action: ActionStop},
|
||||
@ -138,12 +134,6 @@ var (
|
||||
{Resource: ResourceJobServiceMonitor, Action: ActionList},
|
||||
{Resource: ResourceJobServiceMonitor, Action: ActionStop},
|
||||
|
||||
{Resource: ResourceTagRetention, Action: ActionRead},
|
||||
{Resource: ResourceTagRetention, Action: ActionCreate},
|
||||
{Resource: ResourceTagRetention, Action: ActionDelete},
|
||||
{Resource: ResourceTagRetention, Action: ActionList},
|
||||
{Resource: ResourceTagRetention, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceScanner, Action: ActionRead},
|
||||
{Resource: ResourceScanner, Action: ActionCreate},
|
||||
{Resource: ResourceScanner, Action: ActionDelete},
|
||||
@ -153,12 +143,8 @@ var (
|
||||
{Resource: ResourceLabel, Action: ActionRead},
|
||||
{Resource: ResourceLabel, Action: ActionCreate},
|
||||
{Resource: ResourceLabel, Action: ActionDelete},
|
||||
{Resource: ResourceLabel, Action: ActionList},
|
||||
{Resource: ResourceLabel, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceExportCVE, Action: ActionRead},
|
||||
{Resource: ResourceExportCVE, Action: ActionCreate},
|
||||
|
||||
{Resource: ResourceSecurityHub, Action: ActionRead},
|
||||
{Resource: ResourceSecurityHub, Action: ActionList},
|
||||
|
||||
@ -178,9 +164,11 @@ var (
|
||||
{Resource: ResourceMetadata, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceRepository, Action: ActionRead},
|
||||
{Resource: ResourceRepository, Action: ActionCreate},
|
||||
{Resource: ResourceRepository, Action: ActionList},
|
||||
{Resource: ResourceRepository, Action: ActionUpdate},
|
||||
{Resource: ResourceRepository, Action: ActionDelete},
|
||||
{Resource: ResourceRepository, Action: ActionList},
|
||||
{Resource: ResourceRepository, Action: ActionPull},
|
||||
{Resource: ResourceRepository, Action: ActionPush},
|
||||
|
||||
{Resource: ResourceArtifact, Action: ActionRead},
|
||||
{Resource: ResourceArtifact, Action: ActionCreate},
|
||||
@ -197,7 +185,7 @@ var (
|
||||
|
||||
{Resource: ResourceAccessory, Action: ActionList},
|
||||
|
||||
{Resource: ResourceArtifactAddition, Action: ActionCreate},
|
||||
{Resource: ResourceArtifactAddition, Action: ActionRead},
|
||||
|
||||
{Resource: ResourceArtifactLabel, Action: ActionCreate},
|
||||
{Resource: ResourceArtifactLabel, Action: ActionDelete},
|
||||
@ -222,7 +210,17 @@ var (
|
||||
{Resource: ResourceNotificationPolicy, Action: ActionList},
|
||||
{Resource: ResourceNotificationPolicy, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceRegistry, Action: ActionPush},
|
||||
{Resource: ResourceTagRetention, Action: ActionRead},
|
||||
{Resource: ResourceTagRetention, Action: ActionCreate},
|
||||
{Resource: ResourceTagRetention, Action: ActionDelete},
|
||||
{Resource: ResourceTagRetention, Action: ActionList},
|
||||
{Resource: ResourceTagRetention, Action: ActionUpdate},
|
||||
|
||||
{Resource: ResourceLabel, Action: ActionRead},
|
||||
{Resource: ResourceLabel, Action: ActionCreate},
|
||||
{Resource: ResourceLabel, Action: ActionDelete},
|
||||
{Resource: ResourceLabel, Action: ActionList},
|
||||
{Resource: ResourceLabel, Action: ActionUpdate},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -21,12 +21,15 @@ import (
|
||||
commonmodels "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/common/security/local"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/jobservice/job/impl/gdpr"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/member"
|
||||
"github.com/goharbor/harbor/src/pkg/oidc"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
"github.com/goharbor/harbor/src/pkg/user/models"
|
||||
)
|
||||
@ -76,6 +79,8 @@ func NewController() Controller {
|
||||
mgr: user.New(),
|
||||
oidcMetaMgr: oidc.NewMetaMgr(),
|
||||
memberMgr: member.Mgr,
|
||||
taskMgr: task.NewManager(),
|
||||
exeMgr: task.NewExecutionManager(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +93,8 @@ type controller struct {
|
||||
mgr user.Manager
|
||||
oidcMetaMgr oidc.MetaManager
|
||||
memberMgr member.Manager
|
||||
taskMgr task.Manager
|
||||
exeMgr task.ExecutionManager
|
||||
}
|
||||
|
||||
func (c *controller) UpdateOIDCMeta(ctx context.Context, ou *commonmodels.OIDCUser, cols ...string) error {
|
||||
@ -183,10 +190,36 @@ func (c *controller) Delete(ctx context.Context, id int) error {
|
||||
if err != nil {
|
||||
return errors.UnknownError(err).WithMessage("failed to load GDPR setting: %v", err)
|
||||
}
|
||||
if gdprSetting.DeleteUser {
|
||||
return c.mgr.DeleteGDPR(ctx, id)
|
||||
|
||||
if gdprSetting.AuditLogs {
|
||||
userDb, err := c.mgr.Get(ctx, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get user information")
|
||||
}
|
||||
return c.mgr.Delete(ctx, id)
|
||||
params := map[string]interface{}{
|
||||
gdpr.UserNameParam: userDb.Username,
|
||||
}
|
||||
execID, err := c.exeMgr.Create(ctx, job.AuditLogsGDPRCompliantVendorType, -1, task.ExecutionTriggerEvent, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.taskMgr.Create(ctx, execID, &task.Job{
|
||||
Name: job.AuditLogsGDPRCompliantVendorType,
|
||||
Metadata: &job.Metadata{
|
||||
JobKind: job.KindGeneric,
|
||||
},
|
||||
Parameters: params,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if gdprSetting.DeleteUser {
|
||||
err = c.mgr.DeleteGDPR(ctx, id)
|
||||
} else {
|
||||
err = c.mgr.Delete(ctx, id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *controller) List(ctx context.Context, query *q.Query, options ...models.Option) ([]*commonmodels.User, error) {
|
||||
|
@ -127,8 +127,6 @@ func main() {
|
||||
|
||||
web.BConfig.WebConfig.Session.SessionOn = true
|
||||
web.BConfig.WebConfig.Session.SessionName = config.SessionCookieName
|
||||
web.BConfig.MaxMemory = 1 << 35 // (32GB)
|
||||
web.BConfig.MaxUploadSize = 1 << 35 // (32GB)
|
||||
// the core db used for beego session
|
||||
redisCoreURL := os.Getenv("_REDIS_URL_CORE")
|
||||
if len(redisCoreURL) > 0 {
|
||||
@ -163,6 +161,12 @@ func main() {
|
||||
log.Info("initializing configurations...")
|
||||
config.Init()
|
||||
log.Info("configurations initialization completed")
|
||||
|
||||
// default beego max memory and max upload size is 128GB, consider from some AI related image would be large,
|
||||
// also support customize it from the environment variables if the default value cannot satisfy some scenarios.
|
||||
web.BConfig.MaxMemory = config.GetBeegoMaxMemoryBytes()
|
||||
web.BConfig.MaxUploadSize = config.GetBeegoMaxUploadSizeBytes()
|
||||
|
||||
metricCfg := config.Metric()
|
||||
if metricCfg.Enabled {
|
||||
metric.RegisterCollectors()
|
||||
|
@ -97,7 +97,6 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/denverdino/aliyungo v0.0.0-20191227032621-df38c6fa730c // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/dnaeon/go-vcr v1.2.0 // indirect
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
|
29
src/go.sum
29
src/go.sum
@ -37,6 +37,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
github.com/Azure/azure-sdk-for-go v37.2.0+incompatible h1:LTdcd2GK+cv+e7yhWCN8S7yf3eblBypKFZsPfKjCQ7E=
|
||||
github.com/Azure/azure-sdk-for-go v37.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
@ -76,6 +77,7 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
@ -160,25 +162,27 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw=
|
||||
github.com/dhui/dktest v0.3.16/go.mod h1:gYaA3LRmM8Z4vJl2MA0THIigJoZrwOansEOsp+kqxp0=
|
||||
github.com/distribution/distribution v2.8.2+incompatible h1:k9+4DKdOG+quPFZXT/mUsiQrGu9vYCp+dXpuPkuqhk8=
|
||||
github.com/distribution/distribution v2.8.2+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc=
|
||||
github.com/distribution/distribution v2.8.3+incompatible h1:RlpEXBLq/WPXYvBYMDAmBX/SnhD67qwtvW/DzKc8pAo=
|
||||
github.com/distribution/distribution v2.8.3+incompatible/go.mod h1:EgLm2NgWtdKgzF9NpMzUKgzmR7AMmb0VQi2B+ZzDRjc=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
|
||||
github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -191,6 +195,7 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
|
||||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
@ -291,6 +296,7 @@ github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F4
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
@ -368,6 +374,7 @@ github.com/gomodule/redigo v1.8.8/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs0
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
|
||||
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@ -380,6 +387,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -533,6 +541,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
@ -544,6 +553,7 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@ -565,6 +575,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
@ -581,6 +592,7 @@ github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -591,7 +603,9 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
@ -615,6 +629,7 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
|
||||
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
|
||||
@ -672,6 +687,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
@ -684,6 +700,7 @@ github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
@ -733,6 +750,7 @@ github.com/tencentcloud/tencentcloud-sdk-go v1.0.62/go.mod h1:asUz5BPXxgoPGaRgZa
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2 h1:ognci8XPlosGhIHK1OLYSpSpnlhSFeBklfe18zIEwcU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.0.0-rc.2/go.mod h1:HVxBVPUK/+fZMonk4bi1islLa8V3cfnBug0+4dykPzo=
|
||||
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||
@ -872,6 +890,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -1087,6 +1106,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -1217,6 +1237,7 @@ k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
|
||||
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
|
||||
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
|
||||
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
85
src/jobservice/job/impl/gdpr/audit_logs_data_masking.go
Normal file
85
src/jobservice/job/impl/gdpr/audit_logs_data_masking.go
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
package gdpr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/audit"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
)
|
||||
|
||||
const UserNameParam = "username"
|
||||
|
||||
type AuditLogsDataMasking struct {
|
||||
manager audit.Manager
|
||||
userManager user.Manager
|
||||
}
|
||||
|
||||
func (a AuditLogsDataMasking) MaxFails() uint {
|
||||
return 3
|
||||
}
|
||||
|
||||
func (a AuditLogsDataMasking) MaxCurrency() uint {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (a AuditLogsDataMasking) ShouldRetry() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (a AuditLogsDataMasking) Validate(params job.Parameters) error {
|
||||
if params == nil {
|
||||
// Params are required
|
||||
return errors.New("missing job parameters")
|
||||
}
|
||||
_, err := a.parseParams(params)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *AuditLogsDataMasking) init() {
|
||||
if a.manager == nil {
|
||||
a.manager = audit.New()
|
||||
}
|
||||
if a.userManager == nil {
|
||||
a.userManager = user.New()
|
||||
}
|
||||
}
|
||||
|
||||
func (a AuditLogsDataMasking) Run(ctx job.Context, params job.Parameters) error {
|
||||
logger := ctx.GetLogger()
|
||||
logger.Info("GDPR audit logs data masking job started")
|
||||
a.init()
|
||||
username, err := a.parseParams(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Infof("Masking log entries for a user: %s", username)
|
||||
return a.manager.UpdateUsername(ctx.SystemContext(), username, a.userManager.GenerateCheckSum(username))
|
||||
}
|
||||
|
||||
func (a AuditLogsDataMasking) parseParams(params job.Parameters) (string, error) {
|
||||
value, exist := params[UserNameParam]
|
||||
if !exist {
|
||||
return "", fmt.Errorf("param %s not found", UserNameParam)
|
||||
}
|
||||
str, ok := value.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("the value of %s isn't string", UserNameParam)
|
||||
}
|
||||
return str, nil
|
||||
}
|
67
src/jobservice/job/impl/gdpr/audit_logs_data_masking_test.go
Normal file
67
src/jobservice/job/impl/gdpr/audit_logs_data_masking_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
package gdpr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
mockjobservice "github.com/goharbor/harbor/src/testing/jobservice"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/audit"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/user"
|
||||
)
|
||||
|
||||
func TestAuditLogsCleanupJobShouldRetry(t *testing.T) {
|
||||
rep := &AuditLogsDataMasking{}
|
||||
assert.True(t, rep.ShouldRetry())
|
||||
}
|
||||
|
||||
func TestAuditLogsCleanupJobValidateParams(t *testing.T) {
|
||||
const validUsername = "user"
|
||||
var (
|
||||
manager = &audit.Manager{}
|
||||
userManager = &user.Manager{}
|
||||
)
|
||||
|
||||
rep := &AuditLogsDataMasking{
|
||||
manager: manager,
|
||||
userManager: userManager,
|
||||
}
|
||||
err := rep.Validate(nil)
|
||||
// parameters are required
|
||||
assert.Error(t, err)
|
||||
err = rep.Validate(job.Parameters{})
|
||||
// no required username parameter
|
||||
assert.Error(t, err)
|
||||
validParams := job.Parameters{
|
||||
"username": "user",
|
||||
}
|
||||
err = rep.Validate(validParams)
|
||||
// parameters are valid
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := &mockjobservice.MockJobContext{}
|
||||
logger := &mockjobservice.MockJobLogger{}
|
||||
|
||||
ctx.On("GetLogger").Return(logger)
|
||||
userManager.On("GenerateCheckSum", validUsername).Return("hash")
|
||||
manager.On("UpdateUsername", context.TODO(), validUsername, "hash").Return(nil)
|
||||
|
||||
err = rep.Run(ctx, validParams)
|
||||
assert.Nil(t, err)
|
||||
}
|
@ -44,6 +44,8 @@ const (
|
||||
ExecSweepVendorType = "EXECUTION_SWEEP"
|
||||
// ScanAllVendorType: the name of the scan all job
|
||||
ScanAllVendorType = "SCAN_ALL"
|
||||
// AuditLogsGDPRCompliantVendorType : the name of the job which makes audit logs table GDPR-compliant
|
||||
AuditLogsGDPRCompliantVendorType = "AUDIT_LOGS_GDPR_COMPLIANT"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -24,6 +24,8 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job/impl/gdpr"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/api"
|
||||
@ -332,6 +334,7 @@ func (bs *Bootstrap) loadAndRunRedisWorkerPool(
|
||||
"IMAGE_SCAN_ALL": (*legacy.ScanAllScheduler)(nil),
|
||||
job.SystemArtifactCleanupVendorType: (*systemartifact.Cleanup)(nil),
|
||||
job.ExecSweepVendorType: (*task.SweepJob)(nil),
|
||||
job.AuditLogsGDPRCompliantVendorType: (*gdpr.AuditLogsDataMasking)(nil),
|
||||
}); err != nil {
|
||||
// exit
|
||||
return nil, err
|
||||
|
@ -14,7 +14,11 @@
|
||||
|
||||
package metadata
|
||||
|
||||
import "github.com/goharbor/harbor/src/common"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
)
|
||||
|
||||
// Item - Configure item include default value, type, env name
|
||||
type Item struct {
|
||||
@ -181,6 +185,7 @@ var (
|
||||
{Name: common.CacheExpireHours, Scope: SystemScope, Group: BasicGroup, EnvKey: "CACHE_EXPIRE_HOURS", DefaultValue: "24", ItemType: &IntType{}, Editable: false, Description: `The expire hours for cache`},
|
||||
|
||||
{Name: common.GDPRDeleteUser, Scope: SystemScope, Group: GDPRGroup, EnvKey: "GDPR_DELETE_USER", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The flag indicates if a user should be deleted compliant with GDPR.`},
|
||||
{Name: common.GDPRAuditLogs, Scope: SystemScope, Group: GDPRGroup, EnvKey: "GDPR_AUDIT_LOGS", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The flag indicates if an audit logs of a deleted user should be GDPR compliant.`},
|
||||
|
||||
{Name: common.AuditLogForwardEndpoint, Scope: UserScope, Group: BasicGroup, EnvKey: "AUDIT_LOG_FORWARD_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The endpoint to forward the audit log.`},
|
||||
{Name: common.SkipAuditLogDatabase, Scope: UserScope, Group: BasicGroup, EnvKey: "SKIP_LOG_AUDIT_DATABASE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The option to skip audit log in database`},
|
||||
@ -192,5 +197,8 @@ var (
|
||||
|
||||
{Name: common.BannerMessage, Scope: UserScope, Group: BasicGroup, EnvKey: "BANNER_MESSAGE", DefaultValue: "", ItemType: &StringType{}, Editable: true, Description: `The customized banner message for the UI`},
|
||||
{Name: common.QuotaUpdateProvider, Scope: SystemScope, Group: BasicGroup, EnvKey: "QUOTA_UPDATE_PROVIDER", DefaultValue: "db", ItemType: &StringType{}, Editable: false, Description: `The provider for updating quota, 'db' or 'redis' is supported`},
|
||||
|
||||
{Name: common.BeegoMaxMemoryBytes, Scope: SystemScope, Group: BasicGroup, EnvKey: "BEEGO_MAX_MEMORY_BYTES", DefaultValue: fmt.Sprintf("%d", common.DefaultBeegoMaxMemoryBytes), ItemType: &Int64Type{}, Editable: false, Description: `The bytes for limiting the beego max memory, default is 128GB`},
|
||||
{Name: common.BeegoMaxUploadSizeBytes, Scope: SystemScope, Group: BasicGroup, EnvKey: "BEEGO_MAX_UPLOAD_SIZE_BYTES", DefaultValue: fmt.Sprintf("%d", common.DefaultBeegoMaxUploadSizeBytes), ItemType: &Int64Type{}, Editable: false, Description: `The bytes for limiting the beego max upload size, default it 128GB`},
|
||||
}
|
||||
)
|
||||
|
@ -98,4 +98,5 @@ type GroupConf struct {
|
||||
|
||||
type GDPRSetting struct {
|
||||
DeleteUser bool `json:"user_delete,omitempty"`
|
||||
AuditLogs bool `json:"audit_logs"`
|
||||
}
|
||||
|
@ -132,6 +132,16 @@ func GetQuotaUpdateProvider() string {
|
||||
return DefaultMgr().Get(backgroundCtx, common.QuotaUpdateProvider).GetString()
|
||||
}
|
||||
|
||||
// GetBeegoMaxMemoryBytes returns the max memory bytes of beego config
|
||||
func GetBeegoMaxMemoryBytes() int64 {
|
||||
return DefaultMgr().Get(backgroundCtx, common.BeegoMaxMemoryBytes).GetInt64()
|
||||
}
|
||||
|
||||
// GetBeegoMaxUploadSizeBytes returns the max upload size bytes of beego config
|
||||
func GetBeegoMaxUploadSizeBytes() int64 {
|
||||
return DefaultMgr().Get(backgroundCtx, common.BeegoMaxUploadSizeBytes).GetInt64()
|
||||
}
|
||||
|
||||
// WithTrivy returns a bool value to indicate if Harbor's deployed with Trivy.
|
||||
func WithTrivy() bool {
|
||||
return DefaultMgr().Get(backgroundCtx, common.WithTrivy).GetBool()
|
||||
|
@ -186,6 +186,7 @@ func GDPRSetting(ctx context.Context) (*cfgModels.GDPRSetting, error) {
|
||||
}
|
||||
return &cfgModels.GDPRSetting{
|
||||
DeleteUser: DefaultMgr().Get(ctx, common.GDPRDeleteUser).GetBool(),
|
||||
AuditLogs: DefaultMgr().Get(ctx, common.GDPRAuditLogs).GetBool(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,10 @@ const (
|
||||
tagged = `=id AND EXISTS (
|
||||
SELECT 1 FROM tag WHERE tag.artifact_id = T0.id
|
||||
)`
|
||||
// accessory filter: filter out the accessory
|
||||
notacc = `=id AND NOT EXISTS (
|
||||
SELECT 1 FROM artifact_accessory aa WHERE aa.artifact_id = T0.id
|
||||
)`
|
||||
)
|
||||
|
||||
// New returns an instance of the default DAO
|
||||
@ -416,6 +420,7 @@ func setAccessoryQuery(qs beegoorm.QuerySeter, query *q.Query) (beegoorm.QuerySe
|
||||
if query == nil {
|
||||
return qs, nil
|
||||
}
|
||||
qs = qs.FilterRaw("id", "not in (select artifact_id from artifact_accessory)")
|
||||
|
||||
qs = qs.FilterRaw("id", notacc)
|
||||
return qs, nil
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ type DAO interface {
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// Purge the audit log
|
||||
Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error)
|
||||
// UpdateUsername replaces username in matched records
|
||||
UpdateUsername(ctx context.Context, username string, usernameReplace string) error
|
||||
}
|
||||
|
||||
// New returns an instance of the default DAO
|
||||
@ -57,6 +59,15 @@ var allowedMaps = map[string]interface{}{
|
||||
|
||||
type dao struct{}
|
||||
|
||||
func (d *dao) UpdateUsername(ctx context.Context, username string, usernameReplace string) error {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = o.Raw("UPDATE audit_log SET username = ? WHERE username = ?", usernameReplace, username).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// Purge delete expired audit log
|
||||
func (*dao) Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error) {
|
||||
ormer, err := orm.FromContext(ctx)
|
||||
|
@ -40,6 +40,8 @@ type Manager interface {
|
||||
Delete(ctx context.Context, id int64) (err error)
|
||||
// Purge delete the audit log with retention hours
|
||||
Purge(ctx context.Context, retentionHour int, includeOperations []string, dryRun bool) (int64, error)
|
||||
// UpdateUsername Replace all log records username with its hash
|
||||
UpdateUsername(ctx context.Context, username string, replaceWith string) error
|
||||
}
|
||||
|
||||
// New returns a default implementation of Manager
|
||||
@ -53,6 +55,10 @@ type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
func (m *manager) UpdateUsername(ctx context.Context, username string, replaceWith string) error {
|
||||
return m.dao.UpdateUsername(ctx, username, replaceWith)
|
||||
}
|
||||
|
||||
// Count ...
|
||||
func (m *manager) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
return m.dao.Count(ctx, query)
|
||||
|
@ -63,6 +63,8 @@ type Manager interface {
|
||||
// put the id in the pointer of user model, if it does exist, return the user's profile.
|
||||
// This is used for ldap and uaa authentication, such the user can have an ID in Harbor.
|
||||
Onboard(ctx context.Context, user *commonmodels.User) error
|
||||
// GenerateCheckSum generates truncated crc32 checksum from a given string
|
||||
GenerateCheckSum(in string) string
|
||||
}
|
||||
|
||||
// New returns a default implementation of Manager
|
||||
@ -111,9 +113,9 @@ func (m *manager) DeleteGDPR(ctx context.Context, id int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Username = fmt.Sprintf("%s#%d", checkSum(u.Username), u.UserID)
|
||||
u.Email = fmt.Sprintf("%s#%d", checkSum(u.Email), u.UserID)
|
||||
u.Realname = fmt.Sprintf("%s#%d", checkSum(u.Realname), u.UserID)
|
||||
u.Username = fmt.Sprintf("%s#%d", m.GenerateCheckSum(u.Username), u.UserID)
|
||||
u.Email = fmt.Sprintf("%s#%d", m.GenerateCheckSum(u.Email), u.UserID)
|
||||
u.Realname = fmt.Sprintf("%s#%d", m.GenerateCheckSum(u.Realname), u.UserID)
|
||||
u.Deleted = true
|
||||
return m.dao.Update(ctx, u, "username", "email", "realname", "deleted")
|
||||
}
|
||||
@ -231,13 +233,14 @@ func excludeDefaultAdmin(query *q.Query) (qu *q.Query) {
|
||||
return query
|
||||
}
|
||||
|
||||
// GenerateCheckSum generates checksum for a given string
|
||||
func (m *manager) GenerateCheckSum(str string) string {
|
||||
return fmt.Sprintf("%08x", crc32.Checksum([]byte(str), crc32.IEEETable))
|
||||
}
|
||||
|
||||
func injectPasswd(u *commonmodels.User, password string) {
|
||||
salt := utils.GenerateRandomString()
|
||||
u.Password = utils.Encrypt(password, salt, utils.SHA256)
|
||||
u.Salt = salt
|
||||
u.PasswordVersion = utils.SHA256
|
||||
}
|
||||
|
||||
func checkSum(str string) string {
|
||||
return fmt.Sprintf("%08x", crc32.Checksum([]byte(str), crc32.IEEETable))
|
||||
}
|
||||
|
@ -65,9 +65,9 @@ func (m *mgrTestSuite) TestUserDeleteGDPR() {
|
||||
m.dao.On("Update", mock.Anything, testifymock.MatchedBy(
|
||||
func(u *models.User) bool {
|
||||
return u.UserID == 123 &&
|
||||
u.Email == fmt.Sprintf("%s#%d", checkSum("existing@mytest.com"), existingUser.UserID) &&
|
||||
u.Username == fmt.Sprintf("%s#%d", checkSum("existing"), existingUser.UserID) &&
|
||||
u.Realname == fmt.Sprintf("%s#%d", checkSum("RealName"), existingUser.UserID) &&
|
||||
u.Email == fmt.Sprintf("%s#%d", m.mgr.GenerateCheckSum("existing@mytest.com"), existingUser.UserID) &&
|
||||
u.Username == fmt.Sprintf("%s#%d", m.mgr.GenerateCheckSum("existing"), existingUser.UserID) &&
|
||||
u.Realname == fmt.Sprintf("%s#%d", m.mgr.GenerateCheckSum("RealName"), existingUser.UserID) &&
|
||||
u.Deleted == true
|
||||
}),
|
||||
"username",
|
||||
|
@ -88,6 +88,8 @@ export class RobotPermissionsPanelComponent
|
||||
this.candidateActions.push(item?.action);
|
||||
}
|
||||
});
|
||||
this.candidateActions.sort();
|
||||
this.candidateResources.sort();
|
||||
}
|
||||
|
||||
isCandidate(resource: string, action: string): boolean {
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
pkg "github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
@ -296,6 +297,28 @@ func (rAPI *robotAPI) validate(d int64, level string, permissions []*models.Robo
|
||||
if level == robot.LEVELPROJECT && len(permissions) > 1 {
|
||||
return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
|
||||
// to validate the access scope
|
||||
for _, perm := range permissions {
|
||||
if perm.Kind == robot.LEVELSYSTEM {
|
||||
polices := rbac.PoliciesMap["System"]
|
||||
for _, acc := range perm.Access {
|
||||
if !containsAccess(polices, acc) {
|
||||
return errors.New(nil).WithMessage("bad request permission: %s:%s", acc.Resource, acc.Action).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
} else if perm.Kind == robot.LEVELPROJECT {
|
||||
polices := rbac.PoliciesMap["Project"]
|
||||
for _, acc := range perm.Access {
|
||||
if !containsAccess(polices, acc) {
|
||||
return errors.New(nil).WithMessage("bad request permission: %s:%s", acc.Resource, acc.Action).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return errors.New(nil).WithMessage("bad request permission level: %s", perm.Kind).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -364,3 +387,12 @@ func validateName(name string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func containsAccess(policies []*types.Policy, item *models.Access) bool {
|
||||
for _, po := range policies {
|
||||
if po.Resource.String() == item.Resource && po.Action.String() == item.Action {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
@ -129,3 +131,79 @@ func TestValidateName(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsAccess(t *testing.T) {
|
||||
system := rbac.PoliciesMap["System"]
|
||||
systests := []struct {
|
||||
name string
|
||||
acc *models.Access
|
||||
expected bool
|
||||
}{
|
||||
{"System ResourceRegistry push",
|
||||
&models.Access{
|
||||
Resource: rbac.ResourceRegistry.String(),
|
||||
Action: rbac.ActionPush.String(),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"System ResourceProject delete",
|
||||
&models.Access{
|
||||
Resource: rbac.ResourceProject.String(),
|
||||
Action: rbac.ActionDelete.String(),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"System ResourceReplicationPolicy delete",
|
||||
&models.Access{
|
||||
Resource: rbac.ResourceReplicationPolicy.String(),
|
||||
Action: rbac.ActionDelete.String(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range systests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ok := containsAccess(system, tt.acc)
|
||||
if ok != tt.expected {
|
||||
t.Errorf("name: %s, containsAccess() = %#v, want %#v", tt.name, tt.acc, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
project := rbac.PoliciesMap["Project"]
|
||||
protests := []struct {
|
||||
name string
|
||||
acc *models.Access
|
||||
expected bool
|
||||
}{
|
||||
{"Project ResourceLog delete",
|
||||
&models.Access{
|
||||
Resource: rbac.ResourceLog.String(),
|
||||
Action: rbac.ActionDelete.String(),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"Project ResourceMetadata read",
|
||||
&models.Access{
|
||||
Resource: rbac.ResourceMetadata.String(),
|
||||
Action: rbac.ActionRead.String(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"Project ResourceRobot create",
|
||||
&models.Access{
|
||||
Resource: rbac.ResourceRobot.String(),
|
||||
Action: rbac.ActionCreate.String(),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range protests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ok := containsAccess(project, tt.acc)
|
||||
if ok != tt.expected {
|
||||
t.Errorf("name: %s, containsAccess() = %#v, want %#v", tt.name, tt.acc, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +155,20 @@ func (_m *DAO) Purge(ctx context.Context, retentionHour int, includeOperations [
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateUsername provides a mock function with given fields: ctx, username, usernameReplace
|
||||
func (_m *DAO) UpdateUsername(ctx context.Context, username string, usernameReplace string) error {
|
||||
ret := _m.Called(ctx, username, usernameReplace)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||
r0 = rf(ctx, username, usernameReplace)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewDAO creates a new instance of DAO. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewDAO(t interface {
|
||||
|
@ -154,6 +154,20 @@ func (_m *Manager) Purge(ctx context.Context, retentionHour int, includeOperatio
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateUsername provides a mock function with given fields: ctx, username, replaceWith
|
||||
func (_m *Manager) UpdateUsername(ctx context.Context, username string, replaceWith string) error {
|
||||
ret := _m.Called(ctx, username, replaceWith)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
|
||||
r0 = rf(ctx, username, replaceWith)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewManager(t interface {
|
||||
|
@ -102,6 +102,20 @@ func (_m *Manager) DeleteGDPR(ctx context.Context, id int) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
// GenerateCheckSum provides a mock function with given fields: in
|
||||
func (_m *Manager) GenerateCheckSum(in string) string {
|
||||
ret := _m.Called(in)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(in)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id
|
||||
func (_m *Manager) Get(ctx context.Context, id int) (*commonmodels.User, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
@ -21,8 +21,8 @@ class Robot(base.Base, object):
|
||||
base._assert_status_code(200, status_code)
|
||||
return body
|
||||
|
||||
def create_access_list(self, right_map = [True] * 10):
|
||||
_assert_status_code(10, len(right_map), r"Please input full access list for system robot account. Expected {}, while actual input count is {}.")
|
||||
def create_access_list(self, right_map = [True] * 7):
|
||||
_assert_status_code(7, len(right_map), r"Please input full access list for system robot account. Expected {}, while actual input count is {}.")
|
||||
action_pull = "pull"
|
||||
action_push = "push"
|
||||
action_read = "read"
|
||||
@ -33,9 +33,6 @@ class Robot(base.Base, object):
|
||||
("repository", action_pull),
|
||||
("repository", action_push),
|
||||
("artifact", action_del),
|
||||
("helm-chart", action_read),
|
||||
("helm-chart-version", action_create),
|
||||
("helm-chart-version", action_del),
|
||||
("tag", action_create),
|
||||
("tag", action_del),
|
||||
("artifact-label", action_create),
|
||||
@ -50,8 +47,7 @@ class Robot(base.Base, object):
|
||||
return access_list
|
||||
|
||||
def create_project_robot(self, project_name, duration, robot_name = None, robot_desc = None,
|
||||
has_pull_right = True, has_push_right = True, has_chart_read_right = True,
|
||||
has_chart_create_right = True, expect_status_code = 201, expect_response_body = None,
|
||||
has_pull_right = True, has_push_right = True, expect_status_code = 201, expect_response_body = None,
|
||||
**kwargs):
|
||||
if robot_name is None:
|
||||
robot_name = base._random_name("robot")
|
||||
@ -62,20 +58,12 @@ class Robot(base.Base, object):
|
||||
access_list = []
|
||||
action_pull = "pull"
|
||||
action_push = "push"
|
||||
action_read = "read"
|
||||
action_create = "create"
|
||||
if has_pull_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "repository", action = action_pull)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_push_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "repository", action = action_push)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_chart_read_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "helm-chart", action = action_read)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_chart_create_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "helm-chart-version", action = action_create)
|
||||
access_list.append(robotAccountAccess)
|
||||
|
||||
robotaccountPermissions = v2_swagger_client.RobotPermission(kind = "project", namespace = project_name, access = access_list)
|
||||
permission_list = []
|
||||
|
@ -33,7 +33,7 @@ class TestJobServiceDashboard(unittest.TestCase, object):
|
||||
self.registry = Registry()
|
||||
self.scan_all = ScanAll()
|
||||
self.schedule = Schedule()
|
||||
self.job_types = [ "GARBAGE_COLLECTION", "PURGE_AUDIT_LOG", "P2P_PREHEAT", "IMAGE_SCAN", "REPLICATION", "RETENTION", "SCAN_DATA_EXPORT", "SCHEDULER", "SLACK", "SYSTEM_ARTIFACT_CLEANUP", "WEBHOOK", "EXECUTION_SWEEP"]
|
||||
self.job_types = [ "GARBAGE_COLLECTION", "PURGE_AUDIT_LOG", "P2P_PREHEAT", "IMAGE_SCAN", "REPLICATION", "RETENTION", "SCAN_DATA_EXPORT", "SCHEDULER", "SLACK", "SYSTEM_ARTIFACT_CLEANUP", "WEBHOOK", "EXECUTION_SWEEP", "AUDIT_LOGS_GDPR_COMPLIANT"]
|
||||
self.cron_type = "Custom"
|
||||
self.cron = "0 0 0 * * 0"
|
||||
|
||||
|
@ -15,6 +15,7 @@ resource = os.environ.get("RESOURCE")
|
||||
ID_PLACEHOLDER = "(id)"
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
class Permission:
|
||||
|
||||
|
||||
@ -38,6 +39,7 @@ class Permission:
|
||||
elif self.res_id_field and self.payload_id_field and self.id_from_header == True:
|
||||
self.payload[self.payload_id_field] = int(response.headers["Location"].split("/")[-1])
|
||||
|
||||
|
||||
resource_permissions = {}
|
||||
# audit logs permissions start
|
||||
list_audit_logs = Permission("{}/audit-logs".format(harbor_base_url), "GET", 200)
|
||||
@ -167,9 +169,9 @@ resource_permissions["replication-policy"] = replication_and_policy
|
||||
# replication permissions start
|
||||
replication_policy_id = None
|
||||
replication_policy_name = "replication-policy-{}".format(random.randint(1000, 9999))
|
||||
result = urlsplit(harbor_base_url)
|
||||
endpoint_URL = "{}://{}".format(result.scheme, result.netloc)
|
||||
if resource == "replication":
|
||||
result = urlsplit(harbor_base_url)
|
||||
endpoint_URL = "{}://{}".format(result.scheme, result.netloc)
|
||||
replication_registry_payload = {
|
||||
"credential": {
|
||||
"access_key": admin_user_name,
|
||||
@ -225,6 +227,94 @@ replication = [ create_replication_execution, list_replication_execution, read_r
|
||||
resource_permissions["replication"] = replication
|
||||
# replication permissions end
|
||||
|
||||
# scan all permissions start
|
||||
scan_all_weekly_schedule_payload = {
|
||||
"schedule": {
|
||||
"type": "Weekly",
|
||||
"cron": "0 0 0 * * 0"
|
||||
}
|
||||
}
|
||||
scan_all_reset_schedule_payload = {
|
||||
"schedule": {
|
||||
"type": "None",
|
||||
"cron": ""
|
||||
}
|
||||
}
|
||||
create_scan_all_schedule = Permission("{}/system/scanAll/schedule".format(harbor_base_url), "POST", 201, scan_all_weekly_schedule_payload)
|
||||
update_scan_all_schedule = Permission("{}/system/scanAll/schedule".format(harbor_base_url), "PUT", 200, scan_all_reset_schedule_payload)
|
||||
stop_scan_all = Permission("{}/system/scanAll/stop".format(harbor_base_url), "POST", 202)
|
||||
scan_all_metrics = Permission("{}/scans/all/metrics".format(harbor_base_url), "GET", 200)
|
||||
scan_all_schedule_metrics = Permission("{}/scans/schedule/metrics".format(harbor_base_url), "GET", 200)
|
||||
scan_all = [ create_scan_all_schedule, update_scan_all_schedule, stop_scan_all, scan_all_metrics, scan_all_schedule_metrics ]
|
||||
resource_permissions["scan-all"] = scan_all
|
||||
# scan all permissions end
|
||||
|
||||
# system volumes permissions start
|
||||
read_system_volumes = Permission("{}/systeminfo/volumes".format(harbor_base_url), "GET", 200)
|
||||
system_volumes = [ read_system_volumes ]
|
||||
resource_permissions["system-volumes"] = system_volumes
|
||||
# system volumes permissions end
|
||||
|
||||
# jobservice monitor permissions start
|
||||
list_jobservice_pool = Permission("{}/jobservice/pools".format(harbor_base_url), "GET", 200)
|
||||
list_jobservice_pool_worker = Permission("{}/jobservice/pools/{}/workers".format(harbor_base_url, "88888888"), "GET", 200)
|
||||
stop_jobservice_job = Permission("{}/jobservice/jobs/{}".format(harbor_base_url, "88888888"), "PUT", 200)
|
||||
get_jobservice_job_log = Permission("{}/jobservice/jobs/{}/log".format(harbor_base_url, "88888888"), "GET", 500)
|
||||
list_jobservice_queue = Permission("{}/jobservice/queues".format(harbor_base_url), "GET", 200)
|
||||
stop_jobservice = Permission("{}/jobservice/queues/{}".format(harbor_base_url, "88888888"), "PUT", 200, payload={ "action": "stop" })
|
||||
jobservice_monitor = [ list_jobservice_pool, list_jobservice_pool_worker, stop_jobservice_job, get_jobservice_job_log, list_jobservice_queue, stop_jobservice ]
|
||||
resource_permissions["jobservice-monitor"] = jobservice_monitor
|
||||
# jobservice monitor permissions end
|
||||
|
||||
# scanner permissions start
|
||||
scanner_payload = {
|
||||
"name": "scanner-{}".format(random.randint(1000, 9999)),
|
||||
"url": "https://{}".format(random.randint(1000, 9999)),
|
||||
"description": None,
|
||||
"auth": "",
|
||||
"skip_certVerify": False,
|
||||
"use_internal_addr": False
|
||||
}
|
||||
list_scanner = Permission("{}/scanners".format(harbor_base_url), "GET", 200)
|
||||
create_scanner = Permission("{}/scanners".format(harbor_base_url), "POST", 500, payload=scanner_payload)
|
||||
ping_scanner = Permission("{}/scanners/ping".format(harbor_base_url), "POST", 500, payload=scanner_payload)
|
||||
read_scanner = Permission("{}/scanners/{}".format(harbor_base_url, "88888888"), "GET", 404)
|
||||
update_scanner = Permission("{}/scanners/{}".format(harbor_base_url, "88888888"), "PUT", 404, payload=scanner_payload)
|
||||
delete_scanner = Permission("{}/scanners/{}".format(harbor_base_url, "88888888"), "DELETE", 404)
|
||||
set_default_scanner = Permission("{}/scanners/{}".format(harbor_base_url, "88888888"), "PATCH", 404, payload={ "is_default": True })
|
||||
get_scanner_metadata = Permission("{}/scanners/{}/metadata".format(harbor_base_url, "88888888"), "GET", 404)
|
||||
scanner = [ list_scanner, create_scanner, ping_scanner, read_scanner, update_scanner, delete_scanner, set_default_scanner, get_scanner_metadata ]
|
||||
resource_permissions["scanner"] = scanner
|
||||
# scanner permissions end
|
||||
|
||||
# system label permissions start
|
||||
label_payload = {
|
||||
"name": "label-{}".format(random.randint(1000, 9999)),
|
||||
"description": "",
|
||||
"color": "",
|
||||
"scope": "g",
|
||||
"project_id": 0
|
||||
}
|
||||
create_label = Permission("{}/labels".format(harbor_base_url), "POST", 201, label_payload, "id", id_from_header=True)
|
||||
read_label = Permission("{}/labels/{}".format(harbor_base_url, ID_PLACEHOLDER), "GET", 200, payload=label_payload, payload_id_field="id")
|
||||
update_label = Permission("{}/labels/{}".format(harbor_base_url, ID_PLACEHOLDER), "PUT", 200, payload=label_payload, payload_id_field="id")
|
||||
delete_label = Permission("{}/labels/{}".format(harbor_base_url, ID_PLACEHOLDER), "DELETE", 200, payload=label_payload, payload_id_field="id")
|
||||
label = [ create_label, read_label, update_label, delete_label ]
|
||||
resource_permissions["label"] = label
|
||||
# system label permissions end
|
||||
|
||||
# security hub permissions start
|
||||
read_summary = Permission("{}/security/summary".format(harbor_base_url), "GET", 200)
|
||||
list_vul = Permission("{}/security/vul".format(harbor_base_url), "GET", 200)
|
||||
security_hub = [ read_summary, list_vul ]
|
||||
resource_permissions["security-hub"] = security_hub
|
||||
# security hub permissions end
|
||||
|
||||
# catalog permissions start
|
||||
read_catalog = Permission("{}/v2/_catalog".format(endpoint_URL), "GET", 200)
|
||||
catalog = [ read_catalog ]
|
||||
resource_permissions["catalog"] = catalog
|
||||
# catalog permissions end
|
||||
|
||||
|
||||
def main():
|
||||
|
365
tests/apitests/python/test_project_permission.py
Normal file
365
tests/apitests/python/test_project_permission.py
Normal file
@ -0,0 +1,365 @@
|
||||
import copy
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
import urllib3
|
||||
import os
|
||||
|
||||
admin_name = os.environ.get("ADMIN_NAME")
|
||||
admin_password = os.environ.get("ADMIN_PASSWORD")
|
||||
user_name = os.environ.get("USER_NAME")
|
||||
password = os.environ.get("PASSWORD")
|
||||
harbor_base_url = os.environ.get("HARBOR_BASE_URL")
|
||||
resource = os.environ.get("RESOURCE")
|
||||
project_id = os.environ.get("PROJECT_ID")
|
||||
project_name = os.environ.get("PROJECT_NAME")
|
||||
# the source artifact should belong to the provided project, e.g. "nginx"
|
||||
source_artifact_name = os.environ.get("SOURCE_ARTIFACT_NAME")
|
||||
# the source artifact tag should belong to the provided project, e.g. "latest"
|
||||
source_artifact_tag = os.environ.get("SOURCE_ARTIFACT_TAG")
|
||||
id_or_name = None
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
|
||||
class Permission:
|
||||
|
||||
def __init__(self, url, method, expect_status_code, payload=None, need_id_or_name=False, res_id_field=None,
|
||||
payload_id_field=None):
|
||||
self.url = url
|
||||
self.method = method
|
||||
self.expect_status_code = expect_status_code
|
||||
self.payload = payload
|
||||
self.res_id_field = res_id_field
|
||||
self.need_id_or_name = need_id_or_name
|
||||
self.payload_id_field = payload_id_field if payload_id_field else res_id_field
|
||||
|
||||
def call(self):
|
||||
global id_or_name
|
||||
url = self.url
|
||||
if self.need_id_or_name:
|
||||
url = self.url.format(id_or_name)
|
||||
response = requests.request(self.method, url, data=json.dumps(self.payload), verify=False,
|
||||
auth=(user_name, password), headers={
|
||||
"Content-Type": "application/json"
|
||||
})
|
||||
print("response: {}".format(response.text))
|
||||
assert response.status_code == self.expect_status_code, ("Failed to call the {} {}, expected status code is {"
|
||||
"}, but got {}, error msg is {}").format(
|
||||
self.method, self.url, self.expect_status_code, response.status_code, response.text)
|
||||
if self.res_id_field and self.payload_id_field and len(json.loads(response.text)) > 0:
|
||||
id_or_name = json.loads(response.text)[0][self.res_id_field]
|
||||
|
||||
|
||||
# Project permissions:
|
||||
# 1. Resource: label, actions: ['read', 'create', 'update', 'delete', 'list']
|
||||
label_payload = {
|
||||
"color": "#FFFFFF",
|
||||
"description": "Just for testing",
|
||||
"name": "label-name-{}".format(int(round(time.time() * 1000))),
|
||||
"project_id": int(project_id),
|
||||
"scope": "p",
|
||||
"id": None
|
||||
}
|
||||
create_label = Permission("{}/labels".format(harbor_base_url), "POST", 201, label_payload)
|
||||
list_label = Permission("{}/labels?scope=p&project_id={}".format(harbor_base_url, project_id), "GET", 200,
|
||||
label_payload, False, "id", "id")
|
||||
read_label = Permission("{}/labels/{}".format(harbor_base_url, "{}"), "GET", 200, label_payload, True)
|
||||
label_payload_for_update = copy.deepcopy(label_payload)
|
||||
label_payload_for_update["description"] = "For update"
|
||||
update_label = Permission("{}/labels/{}".format(harbor_base_url, "{}"), "PUT", 200, label_payload_for_update, True)
|
||||
delete_label = Permission("{}/labels/{}".format(harbor_base_url, "{}"), "DELETE", 200, label_payload, True)
|
||||
|
||||
# 2. Resource: project, actions: ['read', 'update', 'delete']
|
||||
project_payload_for_update = {"project_name": "test", "metadata": {"public": "false"}, "storage_limit": -1}
|
||||
read_project = Permission("{}/projects/{}".format(harbor_base_url, project_id), "GET", 200, project_payload_for_update,
|
||||
False)
|
||||
update_project = Permission("{}/projects/{}".format(harbor_base_url, project_id), "PUT", 200,
|
||||
project_payload_for_update, False)
|
||||
delete_project = Permission("{}/projects/{}".format(harbor_base_url, project_id), "DELETE", 200,
|
||||
project_payload_for_update, False)
|
||||
deletable_project = Permission("{}/projects/{}/_deletable".format(harbor_base_url, project_id), "GET", 200,
|
||||
project_payload_for_update, False)
|
||||
|
||||
# 3. Resource: metadata actions: ['read', 'list', 'create', 'update', 'delete'],
|
||||
metadata_payload = {
|
||||
"auto_scan": "true"
|
||||
}
|
||||
create_metadata = Permission("{}/projects/{}/metadatas".format(harbor_base_url, project_id), "POST", 200,
|
||||
metadata_payload, False)
|
||||
list_metadata = Permission("{}/projects/{}/metadatas".format(harbor_base_url, project_id), "GET", 200, metadata_payload,
|
||||
False, )
|
||||
read_metadata = Permission("{}/projects/{}/metadatas/auto_scan".format(harbor_base_url, project_id), "GET", 200,
|
||||
metadata_payload, False)
|
||||
metadata_payload_for_update = {
|
||||
"auto_scan": "false"
|
||||
}
|
||||
update_metadata = Permission("{}/projects/{}/metadatas/auto_scan".format(harbor_base_url, project_id), "PUT", 200,
|
||||
metadata_payload_for_update, False)
|
||||
delete_metadata = Permission("{}/projects/{}/metadatas/auto_scan".format(harbor_base_url, project_id), "DELETE", 200,
|
||||
metadata_payload, False)
|
||||
|
||||
# 4. Resource: repository actions: ['read', 'list', 'update', 'delete', 'pull', 'push']
|
||||
# note: pull and push are for docker cli, no API needs them
|
||||
list_repo = Permission("{}/projects/{}/repositories".format(harbor_base_url, project_name), "GET", 200)
|
||||
read_repo = Permission("{}/projects/{}/repositories/does_not_exist".format(harbor_base_url, project_name), "GET", 404)
|
||||
repo_payload_for_update = {
|
||||
}
|
||||
update_repo = Permission("{}/projects/{}/repositories/does_not_exist".format(harbor_base_url, project_name), "PUT", 404,
|
||||
repo_payload_for_update)
|
||||
delete_repo = Permission("{}/projects/{}/repositories/does_not_exist".format(harbor_base_url, project_name), "DELETE",
|
||||
404)
|
||||
|
||||
# 5. Resource artifact actions: ['read', 'list', 'create', 'delete'],
|
||||
list_artifact = Permission("{}/projects/{}/repositories/does_not_exist/artifacts".format(harbor_base_url, project_name),
|
||||
"GET", 200)
|
||||
read_artifact = Permission(
|
||||
"{}/projects/{}/repositories/does_not_exist/artifacts/reference_does_not_exist".format(harbor_base_url,
|
||||
project_name), "GET", 404)
|
||||
copy_artifact = Permission(
|
||||
"{}/projects/{}/repositories/target_repo/artifacts?from={}/{}:{}".format(harbor_base_url, project_name,
|
||||
project_name, source_artifact_name,
|
||||
source_artifact_tag), "POST", 201)
|
||||
delete_artifact = Permission(
|
||||
"{}/projects/{}/repositories/target_repo/artifacts/{}".format(harbor_base_url, project_name, source_artifact_tag),
|
||||
"DELETE", 200)
|
||||
|
||||
# 6. Resource scan actions: ['read', 'create', 'stop']
|
||||
create_scan = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/scan".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "POST", 202)
|
||||
stop_scan = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/scan/stop".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "POST", 202)
|
||||
read_scan = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/scan/0/log".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "get", 404)
|
||||
|
||||
# 7. Resource tag actions: ['list', 'create', 'delete']
|
||||
tag_payload = {
|
||||
"name": "test-{}".format(int(round(time.time() * 1000)))
|
||||
}
|
||||
create_tag = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/tags".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "POST", 201, tag_payload)
|
||||
list_tag = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/tags".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "GET", 200)
|
||||
delete_tag = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/tags/tag_name_does_not_exist".format(harbor_base_url, project_name,
|
||||
source_artifact_name,
|
||||
source_artifact_tag), "DELETE",
|
||||
404)
|
||||
|
||||
# 8. Resource accessory actions: ['list']
|
||||
list_accessory = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/accessories".format(harbor_base_url, project_name,
|
||||
source_artifact_name, source_artifact_tag), "GET",
|
||||
200)
|
||||
|
||||
# 9. Resource artifact-addition actions: ['read']
|
||||
read_artifact_addition_vul = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/additions/vulnerabilities".format(harbor_base_url, project_name,
|
||||
source_artifact_name,
|
||||
source_artifact_tag), "GET", 200)
|
||||
read_artifact_addition_dependencies = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/additions/dependencies".format(harbor_base_url, project_name,
|
||||
source_artifact_name,
|
||||
source_artifact_tag), "GET", 400)
|
||||
|
||||
# 10. Resource artifact-label actions: ['create', 'delete'],
|
||||
artifact_label_payload = copy.deepcopy(label_payload)
|
||||
artifact_label_payload["description"] = "Add label to an artifact"
|
||||
add_label_to_artifact = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/labels".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "POST", 404,
|
||||
artifact_label_payload)
|
||||
delete_artifact_label = Permission(
|
||||
"{}/projects/{}/repositories/{}/artifacts/{}/labels/0".format(harbor_base_url, project_name, source_artifact_name,
|
||||
source_artifact_tag), "DELETE", 404,
|
||||
artifact_label_payload)
|
||||
|
||||
# 11. Resource scanner actions: ['create', 'read']
|
||||
update_project_scanner = Permission("{}/projects/{}/scanner".format(harbor_base_url, project_id), "PUT", 200,
|
||||
{"uuid": "faked_uuid"})
|
||||
read_project_scanner = Permission("{}/projects/{}/scanner".format(harbor_base_url, project_id), "GET", 200)
|
||||
read_project_scanner_candidates = Permission("{}/projects/{}/scanner/candidates".format(harbor_base_url, project_id),
|
||||
"GET", 200)
|
||||
|
||||
# 12. Resource preheat-policy actions: ['read', 'list', 'create', 'update', 'delete']
|
||||
create_preheat_policy = Permission("{}/projects/{}/preheat/policies".format(harbor_base_url, project_name), "POST", 500,
|
||||
{})
|
||||
list_preheat_policy = Permission("{}/projects/{}/preheat/policies".format(harbor_base_url, project_name), "GET", 200)
|
||||
read_preheat_policy = Permission(
|
||||
"{}/projects/{}/preheat/policies/policy_name_does_not_exist".format(harbor_base_url, project_name), "GET", 404)
|
||||
update_preheat_policy = Permission(
|
||||
"{}/projects/{}/preheat/policies/policy_name_does_not_exist".format(harbor_base_url, project_name), "PUT", 500)
|
||||
delete_preheat_policy = Permission(
|
||||
"{}/projects/{}/preheat/policies/policy_name_does_not_exist".format(harbor_base_url, project_name), "DELETE", 404)
|
||||
|
||||
# 13. Resource immutable-tag actions: ['list', 'create', 'update', 'delete']
|
||||
immutable_tag_rule_payload = {
|
||||
"disabled": False,
|
||||
"scope_selectors": {
|
||||
"repository": [{"kind": "doublestar", "decoration": "repoMatches",
|
||||
"pattern": "{}".format(int(round(time.time() * 1000)))}]},
|
||||
"tag_selectors": [
|
||||
{"kind": "doublestar", "decoration": "matches", "pattern": "{}".format(int(round(time.time() * 1000)))}],
|
||||
}
|
||||
create_immutable_tag_rule = Permission("{}/projects/{}/immutabletagrules".format(harbor_base_url, project_id), "POST",
|
||||
201,
|
||||
immutable_tag_rule_payload)
|
||||
list_immutable_tag_rule = Permission("{}/projects/{}/immutabletagrules".format(harbor_base_url, project_id), "GET", 200)
|
||||
update_immutable_tag_rule = Permission("{}/projects/{}/immutabletagrules/0".format(harbor_base_url, project_id), "PUT",
|
||||
404)
|
||||
delete_immutable_tag_rule = Permission("{}/projects/{}/immutabletagrules/0".format(harbor_base_url, project_id),
|
||||
"DELETE", 404)
|
||||
|
||||
# 14. Resource tag-retention actions: ['read', 'list', 'create', 'update', 'delete']
|
||||
tag_retention_rule_payload = {
|
||||
"algorithm": "or",
|
||||
"rules": [
|
||||
{
|
||||
"disabled": False,
|
||||
"action": "retain",
|
||||
"scope_selectors": {
|
||||
"repository": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "repoMatches",
|
||||
"pattern": "**"
|
||||
}
|
||||
]
|
||||
},
|
||||
"tag_selectors": [
|
||||
{
|
||||
"kind": "doublestar",
|
||||
"decoration": "matches",
|
||||
"pattern": "**",
|
||||
"extras": "{\"untagged\":true}"
|
||||
}
|
||||
],
|
||||
"params": {},
|
||||
"template": "always"
|
||||
}
|
||||
],
|
||||
"trigger": {
|
||||
"kind": "Schedule",
|
||||
"references": {},
|
||||
"settings": {
|
||||
"cron": ""
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"level": "project",
|
||||
"ref": int(project_id)
|
||||
}
|
||||
}
|
||||
|
||||
response = requests.request("GET", "{}/projects/{}/metadatas/retention_id".format(harbor_base_url, project_id),
|
||||
data=None, verify=False,
|
||||
auth=(admin_name, admin_password), headers={"Content-Type": "application/json"})
|
||||
create_status_code = 400 if "retention_id" in (json.loads(response.text)) else 201
|
||||
create_tag_retention_rule = Permission("{}/retentions".format(harbor_base_url, project_id), "POST",
|
||||
create_status_code,
|
||||
tag_retention_rule_payload)
|
||||
# get retention_id
|
||||
response1 = requests.request("GET", "{}/projects/{}/metadatas/retention_id".format(harbor_base_url, project_id),
|
||||
data=None, verify=False,
|
||||
auth=(admin_name, admin_password), headers={"Content-Type": "application/json"})
|
||||
retention_id = json.loads(response1.text)["retention_id"]
|
||||
update_retention_payload = copy.deepcopy(tag_retention_rule_payload)
|
||||
update_retention_payload["rules"][0]["disabled"] = True
|
||||
read_tag_retention = Permission("{}/retentions/{}".format(harbor_base_url, retention_id), "GET", 200)
|
||||
update_tag_retention = Permission("{}/retentions/{}".format(harbor_base_url, retention_id), "PUT", 200,
|
||||
update_retention_payload)
|
||||
delete_tag_retention = Permission("{}/retentions/{}".format(harbor_base_url, retention_id), "DELETE", 200)
|
||||
execute_tag_retention = Permission("{}/retentions/{}/executions".format(harbor_base_url, retention_id), "POST", 201)
|
||||
list_tag_retention_execution = Permission("{}/retentions/{}/executions".format(harbor_base_url, retention_id), "GET",
|
||||
200)
|
||||
stop_tag_retention = Permission("{}/retentions/{}/executions/0".format(harbor_base_url, retention_id), "PATCH", 404,
|
||||
{"action": "stop"})
|
||||
list_tag_retention_tasks = Permission("{}/retentions/{}/executions/0/tasks".format(harbor_base_url, retention_id),
|
||||
"GET", 404)
|
||||
read_tag_retention_tasks = Permission("{}/retentions/{}/executions/0/tasks/0".format(harbor_base_url, retention_id),
|
||||
"GET", 404)
|
||||
|
||||
# 15. Resource log actions: ['list']
|
||||
list_log = Permission("{}/projects/{}/logs".format(harbor_base_url, project_name), "GET", 200)
|
||||
|
||||
# 16. Resource notification-policy actions: ['read', 'list', 'create', 'update', 'delete']
|
||||
webhook_payload = {
|
||||
"name": "webhook-{}".format(int(round(time.time() * 1000))),
|
||||
"description": "Just for test",
|
||||
"project_id": int(project_id),
|
||||
"targets": [
|
||||
{
|
||||
"type": "http",
|
||||
"address": "http://test.com",
|
||||
"skip_cert_verify": True,
|
||||
"payload_format": "CloudEvents"
|
||||
}
|
||||
],
|
||||
"event_types": [
|
||||
"PUSH_ARTIFACT"
|
||||
],
|
||||
"enabled": True
|
||||
}
|
||||
|
||||
create_webhook = Permission("{}/projects/{}/webhook/policies".format(harbor_base_url, project_id), "POST",
|
||||
201,
|
||||
webhook_payload)
|
||||
list_webhook = Permission("{}/projects/{}/webhook/policies".format(harbor_base_url, project_id), "GET",
|
||||
200)
|
||||
read_webhook = Permission("{}/projects/{}/webhook/policies/0".format(harbor_base_url, project_id), "GET",
|
||||
404)
|
||||
update_webhook = Permission("{}/projects/{}/webhook/policies/0".format(harbor_base_url, project_id), "PUT",
|
||||
404, {})
|
||||
delete_webhook = Permission("{}/projects/{}/webhook/policies/0".format(harbor_base_url, project_id), "DELETE",
|
||||
404)
|
||||
|
||||
list_webhook_executions = Permission("{}/projects/{}/webhook/policies/0/executions".format(harbor_base_url, project_id),
|
||||
"GET", 404)
|
||||
list_webhook_executions_tasks = Permission(
|
||||
"{}/projects/{}/webhook/policies/0/executions/0/tasks".format(harbor_base_url, project_id), "GET", 404)
|
||||
read_webhook_executions_tasks = Permission(
|
||||
"{}/projects/{}/webhook/policies/0/executions/0/tasks/0/log".format(harbor_base_url, project_id), "GET", 404)
|
||||
list_webhook_events = Permission("{}/projects/{}/webhook/events".format(harbor_base_url, project_id), "GET", 200)
|
||||
|
||||
resource_permissions = {
|
||||
"label": [create_label, list_label, read_label, update_label, delete_label],
|
||||
"project": [read_project, update_project, deletable_project, delete_project],
|
||||
"metadata": [create_metadata, list_metadata, read_metadata, update_metadata, delete_metadata],
|
||||
"repository": [list_repo, read_repo, update_repo, delete_repo],
|
||||
"artifact": [list_artifact, read_artifact, copy_artifact, delete_artifact],
|
||||
"scan": [create_scan, stop_scan, read_scan],
|
||||
"tag": [create_tag, list_tag, delete_tag],
|
||||
"accessory": [list_accessory],
|
||||
"artifact-addition": [read_artifact_addition_vul, read_artifact_addition_dependencies],
|
||||
"artifact-label": [add_label_to_artifact, delete_artifact_label],
|
||||
"scanner": [update_project_scanner, read_project_scanner, read_project_scanner_candidates],
|
||||
"preheat-policy": [create_preheat_policy, list_preheat_policy, read_preheat_policy, update_preheat_policy,
|
||||
delete_preheat_policy],
|
||||
"immutable-tag": [create_immutable_tag_rule, list_immutable_tag_rule, update_immutable_tag_rule,
|
||||
delete_immutable_tag_rule],
|
||||
"tag-retention": [create_tag_retention_rule, read_tag_retention, update_tag_retention, execute_tag_retention,
|
||||
list_tag_retention_execution, stop_tag_retention, list_tag_retention_tasks,
|
||||
read_tag_retention_tasks, delete_tag_retention],
|
||||
"log": [list_log],
|
||||
"notification-policy": [create_webhook, list_webhook, read_webhook, update_webhook, delete_webhook,
|
||||
list_webhook_executions, list_webhook_executions_tasks, read_webhook_executions_tasks,
|
||||
list_webhook_events]
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
for permission in resource_permissions[resource]:
|
||||
print("=================================================")
|
||||
print("call: {} {}".format(permission.method, permission.url))
|
||||
print("payload: {}".format(json.dumps(permission.payload)))
|
||||
print("=================================================\n")
|
||||
permission.call()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -194,10 +194,10 @@ class TestRobotAccount(unittest.TestCase):
|
||||
# In this priviledge check list, make sure that each of lines and rows must
|
||||
# contains both True and False value.
|
||||
check_list = [
|
||||
[True, True, True, True, True, True, False, True, False, True],
|
||||
[False, False, False, False, True, True, False, True, True, False],
|
||||
[True, False, True, False, True, False, True, False, True, True],
|
||||
[False, False, False, True, False, True, False, True, True, False]
|
||||
[True, True, True, False, True, False, True],
|
||||
[False, False, False, False, True, True, False],
|
||||
[True, False, True, True, False, True, True],
|
||||
[False, False, False, False, True, True, False]
|
||||
]
|
||||
access_list_list = []
|
||||
for i in range(len(check_list)):
|
||||
@ -240,12 +240,12 @@ class TestRobotAccount(unittest.TestCase):
|
||||
|
||||
repo_name, tag = push_self_build_image_to_project(project_access["project_name"], harbor_server, ADMIN_CLIENT["username"], ADMIN_CLIENT["password"], "test_create_tag", "latest_1")
|
||||
self.artifact.create_tag(project_access["project_name"], repo_name.split('/')[1], tag, "for_delete", **ADMIN_CLIENT)
|
||||
if project_access["check_list"][6]: #---tag:create---
|
||||
if project_access["check_list"][3]: #---tag:create---
|
||||
self.artifact.create_tag(project_access["project_name"], repo_name.split('/')[1], tag, "1.0", **SYSTEM_RA_CLIENT)
|
||||
else:
|
||||
self.artifact.create_tag(project_access["project_name"], repo_name.split('/')[1], tag, "1.0", expect_status_code = 403, **SYSTEM_RA_CLIENT)
|
||||
|
||||
if project_access["check_list"][7]: #---tag:delete---
|
||||
if project_access["check_list"][4]: #---tag:delete---
|
||||
self.artifact.delete_tag(project_access["project_name"], repo_name.split('/')[1], tag, "for_delete", **SYSTEM_RA_CLIENT)
|
||||
else:
|
||||
self.artifact.delete_tag(project_access["project_name"], repo_name.split('/')[1], tag, "for_delete", expect_status_code = 403, **SYSTEM_RA_CLIENT)
|
||||
@ -253,12 +253,12 @@ class TestRobotAccount(unittest.TestCase):
|
||||
repo_name, tag = push_self_build_image_to_project(project_access["project_name"], harbor_server, ADMIN_CLIENT["username"], ADMIN_CLIENT["password"], "test_create_artifact_label", "latest_1")
|
||||
#Add project level label to artifact
|
||||
label_id, _ = self.label.create_label(project_id = project_access["project_id"], scope = "p", **ADMIN_CLIENT)
|
||||
if project_access["check_list"][8]: #---artifact-label:create---
|
||||
if project_access["check_list"][5]: #---artifact-label:create---
|
||||
self.artifact.add_label_to_reference(project_access["project_name"], repo_name.split('/')[1], tag, int(label_id), **SYSTEM_RA_CLIENT)
|
||||
else:
|
||||
self.artifact.add_label_to_reference(project_access["project_name"], repo_name.split('/')[1], tag, int(label_id), expect_status_code = 403, **SYSTEM_RA_CLIENT)
|
||||
|
||||
if project_access["check_list"][9]: #---scan:create---
|
||||
if project_access["check_list"][6]: #---scan:create---
|
||||
self.scan.scan_artifact(project_access["project_name"], repo_name.split('/')[1], tag, **SYSTEM_RA_CLIENT)
|
||||
else:
|
||||
self.scan.scan_artifact(project_access["project_name"], repo_name.split('/')[1], tag, expect_status_code = 403, **SYSTEM_RA_CLIENT)
|
||||
@ -325,7 +325,7 @@ class TestRobotAccount(unittest.TestCase):
|
||||
self.verify_repository_unpushable(project_access_list, SYSTEM_RA_CLIENT)
|
||||
|
||||
#20. Add a system robot account with all projects coverd;
|
||||
all_true_access_list= self.robot.create_access_list( [True] * 10 )
|
||||
all_true_access_list= self.robot.create_access_list( [True] * 7 )
|
||||
robot_account_Permissions_list = []
|
||||
robot_account_Permissions = v2_swagger_client.RobotPermission(kind = "project", namespace = "*", access = all_true_access_list)
|
||||
robot_account_Permissions_list.append(robot_account_Permissions)
|
||||
|
@ -3,5 +3,5 @@ set -x
|
||||
|
||||
set -e
|
||||
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.3 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.3 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_online GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.4 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
sudo make package_offline GOBUILDTAGS="include_oss include_gcs" VERSIONTAG=dev-gitaction PKGVERSIONTAG=dev-gitaction UIVERSIONTAG=dev-gitaction GOBUILDIMAGE=golang:1.21.4 COMPILETAG=compile_golangimage TRIVYFLAG=true HTTPPROXY= PULL_BASE_FROM_DOCKERHUB=false
|
||||
|
@ -375,13 +375,29 @@ Back Project Home
|
||||
[Arguments] ${project_name}
|
||||
Retry Link Click //a[contains(.,'${project_name}')]
|
||||
|
||||
Should Not Be Signed By Cosign
|
||||
Should Be Signed
|
||||
[Arguments] ${tag}
|
||||
Retry Wait Element Visible //clr-dg-row[contains(.,'${tag}')]//clr-icon[contains(@class,'signed')]
|
||||
|
||||
Should Not Be Signed
|
||||
[Arguments] ${tag}
|
||||
Retry Wait Element Visible //clr-dg-row[contains(.,'${tag}')]//clr-icon[contains(@class,'color-red')]
|
||||
|
||||
Should Be Signed By Cosign
|
||||
[Arguments] ${tag}
|
||||
Retry Wait Element Visible //clr-dg-row[contains(.,'${tag}')]//clr-icon[contains(@class,'signed')]
|
||||
[Arguments] ${tag}=${null} ${digest}=${null}
|
||||
IF '${tag}' != '${null}'
|
||||
Retry Wait Element Visible //clr-dg-row[./clr-expandable-animation/div/div/div/clr-dg-cell/div/clr-tooltip/div/div/span[contains(.,'${tag}')] and .//clr-dg-row[.//img[@title='signature.cosign']]]
|
||||
ELSE
|
||||
Retry Wait Element Visible //clr-dg-row[./clr-expandable-animation/div/div/div/clr-dg-cell/div/a[contains(.,'${digest}')] and .//clr-dg-row[.//img[@title='signature.cosign']]]
|
||||
END
|
||||
|
||||
Should Be Signed By Notation
|
||||
[Arguments] ${tag}=${null} ${digest}=${null}
|
||||
IF '${tag}' != '${null}'
|
||||
Retry Wait Element Visible //clr-dg-row[./clr-expandable-animation/div/div/div/clr-dg-cell/div/clr-tooltip/div/div/span[contains(.,'${tag}')] and .//clr-dg-row[.//img[@title='signature.notation']]]
|
||||
ELSE
|
||||
Retry Wait Element Visible //clr-dg-row[./clr-expandable-animation/div/div/div/clr-dg-cell/div/a[contains(.,'${digest}')] and .//clr-dg-row[.//img[@title='signature.notation']]]
|
||||
END
|
||||
|
||||
Delete Accessory
|
||||
[Arguments] ${tag}
|
||||
|
26
tests/resources/Notation_Util.robot
Normal file
26
tests/resources/Notation_Util.robot
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright Project Harbor Authors
|
||||
#
|
||||
# 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
|
||||
|
||||
*** Settings ***
|
||||
Documentation This resource provides helper functions for docker operations
|
||||
Library OperatingSystem
|
||||
Library Process
|
||||
|
||||
*** Keywords ***
|
||||
Notation Generate Cert
|
||||
Run And Return Rc And Output notation cert generate-test --default wabbit-networks.io
|
||||
|
||||
Notation Sign
|
||||
[Arguments] ${artifact}
|
||||
Wait Unitl Command Success notation sign -d --allow-referrers-api ${artifact}
|
@ -82,6 +82,7 @@ Resource SeleniumUtil.robot
|
||||
Resource Nightly-Util.robot
|
||||
Resource APITest-Util.robot
|
||||
Resource Cosign_Util.robot
|
||||
Resource Notation_Util.robot
|
||||
Resource Imgpkg-Util.robot
|
||||
Resource Webhook-Util.robot
|
||||
Resource TestCaseBody.robot
|
||||
|
@ -474,19 +474,28 @@ Test Case - Copy A Image And Accessory
|
||||
Create An New Project And Go Into Project ${source_project}
|
||||
|
||||
Push Image With Tag ${ip} ${user} ${pwd} ${source_project} ${image} ${tag}
|
||||
Cosign Generate Key Pair
|
||||
Docker Login ${ip} ${user} ${pwd}
|
||||
Cosign Generate Key Pair
|
||||
Cosign Sign ${ip}/${source_project}/${image}:${tag}
|
||||
Docker Logout ${ip}
|
||||
Notation Generate Cert
|
||||
Notation Sign ${ip}/${source_project}/${image}:${tag}
|
||||
|
||||
Go Into Repo ${source_project} ${image}
|
||||
Should Be Signed ${tag}
|
||||
Retry Button Click ${artifact_list_accessory_btn}
|
||||
Should Be Signed By Cosign ${tag}
|
||||
Should Be Signed By Notation ${tag}
|
||||
|
||||
Copy Image ${tag} ${target_project} ${image}
|
||||
|
||||
Retry Double Keywords When Error Go Into Project ${target_project} Retry Wait Until Page Contains ${image}
|
||||
Go Into Repo ${target_project} ${image}
|
||||
Retry Wait Until Page Contains Element //clr-dg-row[contains(.,${tag})]
|
||||
Should Be Signed ${tag}
|
||||
Retry Button Click ${artifact_list_accessory_btn}
|
||||
Should Be Signed By Cosign ${tag}
|
||||
Should Be Signed By Notation ${tag}
|
||||
Docker Logout ${ip}
|
||||
Close Browser
|
||||
|
||||
Test Case - Create An New Project With Quotas Set
|
||||
@ -772,14 +781,14 @@ Test Case - Cosign And Cosign Deployment Security Policy
|
||||
Push Image With Tag ${ip} ${user} ${pwd} project${d} ${image} ${tag}
|
||||
Go Into Project project${d}
|
||||
Go Into Repo project${d} ${image}
|
||||
Should Not Be Signed By Cosign ${tag}
|
||||
Should Not Be Signed ${tag}
|
||||
Cannot Pull Image ${ip} ${user} ${pwd} project${d} ${image}:${tag} err_msg=The image is not signed by cosign.
|
||||
Cosign Generate Key Pair
|
||||
Cosign Verify ${ip}/project${d}/${image}:${tag} ${false}
|
||||
|
||||
Cosign Sign ${ip}/project${d}/${image}:${tag}
|
||||
Cosign Verify ${ip}/project${d}/${image}:${tag} ${true}
|
||||
Retry Double Keywords When Error Retry Element Click ${artifact_list_refresh_btn} Should Be Signed By Cosign ${tag}
|
||||
Retry Double Keywords When Error Retry Element Click ${artifact_list_refresh_btn} Should Be Signed ${tag}
|
||||
Pull image ${ip} ${user} ${pwd} project${d} ${image}:${tag}
|
||||
|
||||
Retry Double Keywords When Error Delete Accessory ${tag} Should be Accessory deleted ${tag}
|
||||
|
@ -389,16 +389,16 @@ Test Case - Robot Account Do Replication
|
||||
Logout Harbor
|
||||
Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Image Should Be Replicated To Project project_dest${d} ${image1}
|
||||
Should Be Signed By Cosign ${tag1}
|
||||
Should Be Signed ${tag1}
|
||||
Image Should Be Replicated To Project project_dest${d} ${image2}
|
||||
Should Be Signed By Cosign ${tag2}
|
||||
Should Be Signed ${tag2}
|
||||
Back Project Home project_dest${d}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Should Be Signed By Cosign ${index_tag}
|
||||
Should Be Signed ${index_tag}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image1_short_sha256}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed ${image2_short_sha256}
|
||||
# pull mode
|
||||
Logout Harbor
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
@ -409,16 +409,16 @@ Test Case - Robot Account Do Replication
|
||||
Check Latest Replication Job Status Succeeded
|
||||
Check Latest Replication Enabled Copy By Chunk
|
||||
Image Should Be Replicated To Project project_dest${d} ${image1}
|
||||
Should Be Signed By Cosign ${tag1}
|
||||
Should Be Signed ${tag1}
|
||||
Image Should Be Replicated To Project project_dest${d} ${image2}
|
||||
Should Be Signed By Cosign ${tag2}
|
||||
Should Be Signed ${tag2}
|
||||
Back Project Home project_dest${d}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Should Be Signed By Cosign ${index_tag}
|
||||
Should Be Signed ${index_tag}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image1_short_sha256}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed ${image2_short_sha256}
|
||||
Close Browser
|
||||
|
||||
Test Case - Replication Triggered By Events
|
||||
@ -468,28 +468,28 @@ Test Case - Replication Triggered By Events
|
||||
Logout Harbor
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Go Into Repo project${d} ${image1}
|
||||
Should Be Signed By Cosign ${tag1}
|
||||
Should Be Signed ${tag1}
|
||||
Go Into Repo project${d} ${index}
|
||||
Should Be Signed By Cosign ${index_tag}
|
||||
Should Be Signed ${index_tag}
|
||||
Go Into Repo project${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image1_short_sha256}
|
||||
Go Into Repo project${d} ${image2}
|
||||
Should Not Be Signed By Cosign ${tag2}
|
||||
Should Not Be Signed ${tag2}
|
||||
Go Into Repo project${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed ${image2_short_sha256}
|
||||
Logout Harbor
|
||||
|
||||
Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
Go Into Repo project_dest${d} ${image1}
|
||||
Should Be Signed By Cosign ${tag1}
|
||||
Should Be Signed ${tag1}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Should Be Signed By Cosign ${index_tag}
|
||||
Should Be Signed ${index_tag}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image1_short_sha256}
|
||||
Go Into Repo project_dest${d} ${image2}
|
||||
Should Not Be Signed By Cosign ${tag2}
|
||||
Should Not Be Signed ${tag2}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed By Cosign ${image2_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Not Be Signed ${image2_short_sha256}
|
||||
Logout Harbor
|
||||
# delete
|
||||
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
@ -498,13 +498,13 @@ Test Case - Replication Triggered By Events
|
||||
Repo Not Exist project${d} ${image2}
|
||||
Go Into Repo project${d} ${image1}
|
||||
Retry Double Keywords When Error Delete Accessory ${tag1} Should be Accessory deleted ${tag1}
|
||||
Should Not Be Signed By Cosign ${tag1}
|
||||
Should Not Be Signed ${tag1}
|
||||
Go Into Repo project${d} ${index}
|
||||
Retry Double Keywords When Error Delete Accessory ${index_tag} Should be Accessory deleted ${index_tag}
|
||||
Should Not Be Signed By Cosign ${index_tag}
|
||||
Should Not Be Signed ${index_tag}
|
||||
Click Index Achieve ${index_tag}
|
||||
Retry Double Keywords When Error Delete Accessory ${image1_short_sha256} Should be Accessory deleted ${image1_short_sha256}
|
||||
Should Not Be Signed By Cosign ${image1_short_sha256}
|
||||
Should Not Be Signed ${image1_short_sha256}
|
||||
Logout Harbor
|
||||
|
||||
Sign In Harbor https://${ip1} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
|
||||
@ -512,12 +512,12 @@ Test Case - Replication Triggered By Events
|
||||
Wait Until Page Contains We couldn't find any artifacts!
|
||||
Go Into Repo project_dest${d} ${image1}
|
||||
Should be Accessory deleted ${tag1}
|
||||
Should Not Be Signed By Cosign ${tag1}
|
||||
Should Not Be Signed ${tag1}
|
||||
Go Into Repo project_dest${d} ${index}
|
||||
Should be Accessory deleted ${index_tag}
|
||||
Should Not Be Signed By Cosign ${index_tag}
|
||||
Should Not Be Signed ${index_tag}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should be Accessory deleted ${image1_short_sha256}
|
||||
Should Not Be Signed By Cosign ${image1_short_sha256}
|
||||
Should Not Be Signed ${image1_short_sha256}
|
||||
Close Browser
|
||||
|
||||
Test Case - Enable Replication Of Cosign Deployment Security Policy
|
||||
@ -595,15 +595,15 @@ Test Case - Enable Replication Of Cosign Deployment Security Policy
|
||||
Repo Exist project_pull_dest${d} ${image2}
|
||||
Repo Exist project_pull_dest${d} ${index}
|
||||
Go Into Repo project_pull_dest${d} ${image1}
|
||||
Should Be Signed By Cosign ${tag1}
|
||||
Should Be Signed ${tag1}
|
||||
Go Into Repo project_pull_dest${d} ${image2}
|
||||
Should Be Signed By Cosign ${tag2}
|
||||
Should Be Signed ${tag2}
|
||||
Go Into Repo project_pull_dest${d} ${index}
|
||||
Should Be Signed By Cosign ${index_tag}
|
||||
Should Be Signed ${index_tag}
|
||||
Go Into Repo project_pull_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image1_short_sha256}
|
||||
Go Into Repo project_pull_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image2_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image2_short_sha256}
|
||||
# check project_push_dest
|
||||
Go Into Project project_push_dest${d}
|
||||
Switch To Project Repo
|
||||
@ -611,15 +611,15 @@ Test Case - Enable Replication Of Cosign Deployment Security Policy
|
||||
Repo Exist project_push_dest${d} ${image2}
|
||||
Repo Exist project_push_dest${d} ${index}
|
||||
Go Into Repo project_push_dest${d} ${image1}
|
||||
Should Be Signed By Cosign ${tag1}
|
||||
Should Be Signed ${tag1}
|
||||
Go Into Repo project_push_dest${d} ${image2}
|
||||
Should Be Signed By Cosign ${tag2}
|
||||
Should Be Signed ${tag2}
|
||||
Go Into Repo project_push_dest${d} ${index}
|
||||
Should Be Signed By Cosign ${index_tag}
|
||||
Should Be Signed ${index_tag}
|
||||
Go Into Repo project_push_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image1_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image1_short_sha256}
|
||||
Go Into Repo project_push_dest${d} ${index}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed By Cosign ${image2_short_sha256}
|
||||
Retry Double Keywords When Error Click Index Achieve ${index_tag} Should Be Signed ${image2_short_sha256}
|
||||
Close Browser
|
||||
|
||||
Test Case - Carvel Imgpkg Copy To Harbor
|
||||
|
Loading…
Reference in New Issue
Block a user