Skip to content

Conversation

michaelalang
Copy link
Contributor

This PR adds an new /api/v1/superuser/config endpoint that shall return all Flask configuration set to have compliance proof for various Security policies like PCI-DSS4.0.

The response is structured in json format with main keys:

  • config
  • warning
  • env
  • schema

The config section returns all config.yaml and defaulted parameters used in the Flask App framework found in the schema section.

The warning section returns all config.yaml and defaulted parameters not found in the schema section.

The env section returns all Environment variables configured.

The schema section returns the defined utils.config.CONFIG_SCHEMA types

Known sensitive fields are obfuscated with ten * and the original length of the field:
Example:

"password": "******** (8)" 

PR included sensitive classified key names:

  • password
  • db_uri
  • secret_key
  • access_key
  • enable_health_debug_secret
  • client_secret
  • s3_access_key
  • s3_secret_key
  • cloudfront_key_id
  • sts_user_access_key
  • sts_user_secret_key
  • ldap_admin_passwd
  • page_token_key
  • security_scanner_v4_psk
  • database_secret_key

Example Output known parameters from the endpoint:

curl -s https://quay.example.com/api/v1/superuser/config -H "Authorization: Bearer ..." | jq -r .config 

{
  "SESSION_COOKIE_SECURE": true,
  "PREFERRED_URL_SCHEME": "https",
...
  "ALLOW_PULLS_WITHOUT_STRICT_LOGGING": true,
  "AUTHENTICATION_TYPE": "LDAP",
  "AVATAR_KIND": "local",
  "BLACKLISTED_EMAIL_DOMAINS": [],
  "BLACKLIST_V2_SPEC": "<1.6.0",
  "BRANDING": {
    "logo": "/static/img/RH_Logo_Quay_Black_UX-horizontal.svg",
    "footer_img": "/static/img/RedHat.svg",
    "footer_url": "https://access.redhat.com/documentation/en-us/red_hat_quay/3/"
  },
  "BROWSER_API_CALLS_XHR_ONLY": false,
  "BUILDLOGS_REDIS": {
    "host": "redis",
    "password": "******** (8)",
    "port": 6379
  },
  "CLEAN_BLOB_UPLOAD_FOLDER": true,
  "CONTACT_INFO": [],
  "CORS_ORIGIN": "*",
  "CREATE_NAMESPACE_ON_PUSH": true,
  "CREATE_PRIVATE_REPO_ON_PUSH": true,
  "DB_CONNECTION_ARGS": {
    "max_connections": 30,
    "autorollback": true,
    "stale_timeout": 300,
    "keepalives": 1,
    "keepalives_idle": 10,
    "keepalives_interval": 2,
    "keepalives_count": 3,
    "tcp_user_timeout": 1000
  },
  "DB_URI": "********** (185)",
  "DEFAULT_SYSTEM_REJECT_QUOTA_BYTES": 0,
  "DEFAULT_TAG_EXPIRATION": "2w",
  "DISTRIBUTED_STORAGE_CONFIG": {
    "default": [
      "RadosGWStorage",
      {
        "access_key": "********** (20)",
        "bucket_name": "quay-registry",
        "hostname": "s3.example.com",
        "is_secure": true,
        "port": 443,
        "secret_key": "********** (40)",
        "storage_path": "/datastorage/registry",
        "region_name": "us-east-1"
      }
    ]
  },
  "DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS": [
    "default"
  ],
  "DISTRIBUTED_STORAGE_PREFERENCE": [
    "default"
  ],
  "DOCUMENTATION_ROOT": "https://access.redhat.com/documentation/en-us/red_hat_quay/3/",
  "ENABLE_HEALTH_DEBUG_SECRET": "******** (8)",
  "ENTITLEMENT_RECONCILIATION_MARKETPLACE_ENDPOINT": "",
  "ENTITLEMENT_RECONCILIATION_USER_ENDPOINT": "",
  "EXPIRED_APP_SPECIFIC_TOKEN_GC": "1d",
  "EXPORT_COMPLIANCE_ENDPOINT": "",
  "EXTERNAL_TLS_TERMINATION": true,
  "FEATURE_ACTION_LOG_ROTATION": false,
  "FEATURE_ADVERTISE_V2": true,
  "FEATURE_AGGREGATED_LOG_COUNT_RETRIEVAL": true,
  "FEATURE_ANONYMOUS_ACCESS": true,
  "FEATURE_APP_SPECIFIC_TOKENS": true,
  "FEATURE_AUTO_PRUNE": false,
  "FEATURE_BITBUCKET_BUILD": false,
  "FEATURE_BLACKLISTED_EMAILS": false,
  "FEATURE_BUILD_SUPPORT": false,
  "FEATURE_CHANGE_TAG_EXPIRATION": true,
  "FEATURE_DIRECT_LOGIN": true,
  "FEATURE_ENTITLEMENT_RECONCILIATION": false,
  "FEATURE_EXPORT_COMPLIANCE": false,
  "FEATURE_EXTENDED_REPOSITORY_NAMES": true,
  "FEATURE_FIPS": false,
  "FEATURE_GARBAGE_COLLECTION": true,
  "FEATURE_GITHUB_BUILD": false,
  "FEATURE_GITHUB_LOGIN": false,
  "FEATURE_GITLAB_BUILD": false,
  "FEATURE_GOOGLE_LOGIN": false,
  "FEATURE_INVITE_ONLY_USER_CREATION": false,
  "FEATURE_LIBRARY_SUPPORT": true,
  "FEATURE_LOG_EXPORT": true,
  "FEATURE_MAILING": false,
  "FEATURE_NONSUPERUSER_TEAM_SYNCING_SETUP": false,
  "FEATURE_PARTIAL_USER_AUTOCOMPLETE": true,
  "FEATURE_PERMANENT_SESSIONS": true,
  "FEATURE_PROXY_CACHE": true,
...

Example Output unknown parameters from the endpoint:

$ curl -s https://quay.example.com/api/v1/superuser/config -H "Authorization: Bearer ..." | jq -r '.warning'
{
  "DEBUG": false,
  "TESTING": false,
  "PROPAGATE_EXCEPTIONS": true,
  "SECRET_KEY": "********** (36)",
  "USE_X_SENDFILE": false,
  "APPLICATION_ROOT": "/",
  "SESSION_COOKIE_NAME": "_csrf_token",
  "SESSION_COOKIE_HTTPONLY": true,
  "SESSION_COOKIE_SAMESITE": "Lax",
  "SESSION_REFRESH_EACH_REQUEST": true,
  "SEND_FILE_MAX_AGE_DEFAULT": 0,
  "TRAP_HTTP_EXCEPTIONS": false,
  "EXPLAIN_TEMPLATE_LOADING": false,
  "MAX_COOKIE_SIZE": 4093,
  "ACCOUNT_RECOVERY_MODE": false,
  "ANALYTICS_TYPE": "FakeAnalytics",
...

Example Output Environment variables from the endpoint:

$ curl -s https://quay.example.com/api/v1/superuser/config -H "Authorization: Bearer ..." | jq -r '.env'
{
  "REDIS_PORT_6379_TCP_PROTO": "tcp",
  "PYTHONUNBUFFERED": "1",
  "LC_ALL": "C.UTF-8",
  "REDIS_PORT": "tcp://172.31.223.51:6379",
  "IGNORE_VALIDATION": "True",
  "WORKER_COUNT_SECSCAN": "1",
  "QUAY_PORT_8080_TCP_PORT": "8080",
  "DB_CONNECTION_POOLING": "true",
  "LANG": "C.UTF-8",
  "TZ": "UTC",
  "WORKER_COUNT_REGISTRY": "10",
  "HOSTNAME": "quay-5bf8bd7575-7824q",
  "QUAYDIR": "/quay-registry",
  "REDIS_PORT_6379_TCP_ADDR": "172.31.223.51",
  "QUAYCONF": "/quay-registry/conf",
  "REDIS_PORT_6379_TCP": "tcp://172.31.223.51:6379",
  "DB_CONNECTION_POOLING_REGISTRY": "true",
  "QUAY_SERVICE_HOST": "172.31.109.228",
  "QUAY_PORT_9091_TCP_PROTO": "tcp",
  "REDIS_SERVICE_PORT": "6379",
  "PYTHONIOENCODING": "UTF-8",
  "KUBERNETES_PORT_443_TCP_PROTO": "tcp",
  "KUBERNETES_PORT_443_TCP_ADDR": "172.31.0.1",
  "container": "oci",
  "QUAYPATH": "/quay-registry",
  "QUAY_PORT_9091_TCP_ADDR": "172.31.109.228",
  "USERS_DEBUG": "0",
  "QUAY_CONFIG_READ_ONLY_FIELD_GROUPS": "\"\"",
  "KUBERNETES_PORT": "tcp://172.31.0.1:443",
  "QUAY_SERVICE_PORT_HTTP_8080": "8080",
  "QUAY_PORT_8080_TCP": "tcp://172.31.109.228:8080",
  "PWD": "/quay-registry",
  "QUAY_PORT_8080_TCP_ADDR": "172.31.109.228",
  "QUAY_PORT_8080_TCP_PROTO": "tcp",
  "HOME": "/",
  "QUAY_SERVICE_PORT_HTTP_9091": "9091",
  "REDIS_SERVICE_PORT_REDIS": "6379",
  "RED_HAT_QUAY": "true",
  "KUBERNETES_SERVICE_PORT_HTTPS": "443",
  "REDIS_SERVICE_HOST": "172.31.223.51",
  "KUBERNETES_PORT_443_TCP_PORT": "443",
  "KUBERNETES_PORT_443_TCP": "tcp://172.31.0.1:443",
  "REDIS_PORT_6379_TCP_PORT": "6379",
  "DEBUGLOG": "true",
  "USERS_DEBUG": "0",
  ...

@app-sre-bot
Copy link
Collaborator

Can one of the admins verify this patch?

Copy link

codecov bot commented Sep 24, 2024

Codecov Report

Attention: Patch coverage is 87.50000% with 6 lines in your changes missing coverage. Please review.

Project coverage is 70.56%. Comparing base (91386ae) to head (996ab52).
Report is 34 commits behind head on master.

Files with missing lines Patch % Lines
endpoints/api/superuser.py 87.23% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3253      +/-   ##
==========================================
+ Coverage   70.54%   70.56%   +0.01%     
==========================================
  Files         443      443              
  Lines       42103    42151      +48     
  Branches     4787     4800      +13     
==========================================
+ Hits        29701    29743      +42     
- Misses      10711    10717       +6     
  Partials     1691     1691              
Flag Coverage Δ
unit 70.56% <87.50%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@michaelalang
Copy link
Contributor Author

The cypress test failing is not related to the change commited ...

  │ ✖  repository-autopruning.cy.ts             01:03       14       13        1        -        - │

@ibazulic
Copy link
Member

Why not modify the /config endpoint that exists for the same reason? This is not a super user thing.

def config():

@michaelalang
Copy link
Contributor Author

Quote: Why not modify the /config endpoint that exists for the same reason? This is not a super user thing.

Because it should be a protected endpoint and only available to SuperUsers ... Had that initially with /v1/config but as obviously seen, to sensitive to be available unprotected ...

@ibazulic
Copy link
Member

It cannot be a protected endpoint because the UI depends on it:

export async function fetchQuayConfig() {

Super user endpoints are protected with superuser authentication.

@michaelalang
Copy link
Contributor Author

@ibazulic but that should be /config not /api/v1/superuser/config ...

@ibazulic
Copy link
Member

I'm asking because the output is the same as what you're trying to acheive. At least visually. Maybe I'm wrong? Can you compare the two outputs?

@michaelalang
Copy link
Contributor Author

Sure ... here's the original /config

$ curl -s  https://quay.example.com/config | jq -r 
{
  "account_recovery_mode": false,
  "config": {
    "AUTHENTICATION_TYPE": "LDAP",
    "AVATAR_KIND": "local",
    "BRANDING": {
      "footer_img": "/static/img/RedHat.svg",
      "footer_url": "https://access.redhat.com/documentation/en-us/red_hat_quay/3/",
      "logo": "/static/img/RH_Logo_Quay_Black_UX-horizontal.svg"
    },
    "CHANNEL_COLORS": [
      "#969696",
      "#aec7e8",
      "#ff7f0e",
      "#ffbb78",
      "#2ca02c",
      "#98df8a",
      "#d62728",
      "#ff9896",
      "#9467bd",
      "#c5b0d5",
      "#8c564b",
      "#c49c94",
      "#e377c2",
      "#f7b6d2",
      "#7f7f7f",
      "#c7c7c7",
      "#bcbd22",
      "#1f77b4",
      "#17becf",
      "#9edae5",
      "#393b79",
      "#5254a3",
      "#6b6ecf",
      "#9c9ede",
      "#9ecae1",
      "#31a354",
      "#b5cf6b",
      "#a1d99b",
      "#8c6d31",
      "#ad494a",
      "#e7ba52",
      "#a55194"
    ],
    "CONTACT_INFO": [],
    "DEBUG": false,
    "DOCUMENTATION_ROOT": "https://access.redhat.com/documentation/en-us/red_hat_quay/3/",
    "FEATURE_PROXY_CACHE": true,
    "FEATURE_QUOTA_MANAGEMENT": true,
    "FEATURE_REPO_MIRROR": true,
    "LOCAL_OAUTH_HANDLER": "/oauth/localapp",
    "PERMANENTLY_DELETE_TAGS": false,
    "PREFERRED_URL_SCHEME": "https",
    "QUOTA_BACKFILL": true,
    "RECAPTCHA_SITE_KEY": null,
    "REGISTRY_TITLE": "Example Container Registry",
    "REGISTRY_TITLE_SHORT": "Example",
    "SEARCH_MAX_RESULT_PAGE_COUNT": 10,
    "SEARCH_RESULTS_PER_PAGE": 10,
    "SENTRY_PUBLIC_DSN": null,
    "SERVER_HOSTNAME": "quay.example.com",
    "SETUP_COMPLETE": true,
    "STATIC_SITE_BUCKET": null,
    "TAG_EXPIRATION_OPTIONS": [
      "0s",
      "1d",
      "1w",
      "2w",
      "4w"
    ],
    "TERMS_OF_SERVICE_URL": "",
    "UI_DELAY_AFTER_WRITE_SECONDS": 3,
    "UI_V2_FEEDBACK_FORM": "https://7qdvkuo9rkj.typeform.com/to/XH5YE79P"
  },
  "external_login": [
    {
      "config": {
        "CLIENT_ID": "Home",
        "OIDC": true
      },
      "icon": "fa-user-circle",
      "id": "Example",
      "title": "Example"
    }
  ],
  "features": {
    "ACI_CONVERSION": false,
    "ACTION_LOG_ROTATION": false,
    "ADVERTISE_V2": true,
    "AGGREGATED_LOG_COUNT_RETRIEVAL": true,
    "ANONYMOUS_ACCESS": true,
    "APP_REGISTRY": false,
    "APP_SPECIFIC_TOKENS": true,
    "AUTO_PRUNE": false,
    "BILLING": false,
    "BITBUCKET_BUILD": false,
    "BLACKLISTED_EMAILS": false,
    "BUILD_SUPPORT": false,
    "CHANGE_TAG_EXPIRATION": true,
    "CLEAR_EXPIRED_RAC_ENTRIES": false,
    "DIRECT_LOGIN": true,
    "DISABLE_PULL_LOGS_FOR_FREE_NAMESPACES": false,
    "ENTITLEMENT_RECONCILIATION": false,
    "EXPORT_COMPLIANCE": false,
    "EXTENDED_REPOSITORY_NAMES": true,
    "FIPS": false,
    "FULL_CONFIG_SHOW": true,
    "GARBAGE_COLLECTION": true,
    "GENERAL_OCI_SUPPORT": true,
    "GITHUB_BUILD": false,
    "GITHUB_LOGIN": false,
    "GITLAB_BUILD": false,
    "GOOGLE_LOGIN": false,
    "HELM_OCI_SUPPORT": true,
    "INVITE_ONLY_USER_CREATION": false,
    "LIBRARY_SUPPORT": true,
    "LOG_EXPORT": true,
    "MAILING": false,
    "MANIFEST_SIZE_BACKFILL": true,
    "NAMESPACE_GARBAGE_COLLECTION": true,
    "NONSUPERUSER_TEAM_SYNCING_SETUP": false,
    "PARTIAL_USER_AUTOCOMPLETE": true,
    "PERMANENT_SESSIONS": true,
    "PROXY_CACHE": true,
    "PROXY_STORAGE": true,
    "PUBLIC_CATALOG": false,
    "QUOTA_MANAGEMENT": true,
    "RATE_LIMITS": false,
    "READER_BUILD_LOGS": false,
    "RECAPTCHA": false,
    "REPOSITORY_ACTION_COUNTER": true,
    "REPOSITORY_GARBAGE_COLLECTION": true,
    "REPO_MIRROR": true,
    "REQUIRE_ENCRYPTED_BASIC_AUTH": false,
    "REQUIRE_TEAM_INVITE": true,
    "RESTRICTED_USERS": false,
    "RESTRICTED_V1_PUSH": true,
    "RH_MARKETPLACE": false,
    "SECURITY_NOTIFICATIONS": true,
    "SECURITY_SCANNER": false,
    "SECURITY_SCANNER_NOTIFY_ON_NEW_INDEX": true,
    "SIGNING": false,
    "STORAGE_REPLICATION": false,
    "SUPERUSERS_FULL_ACCESS": true,
    "SUPERUSERS_ORG_CREATION_ONLY": false,
    "SUPER_USERS": true,
    "TEAM_SYNCING": true,
    "UI_DELAY_AFTER_WRITE": false,
    "UI_V2": true,
    "UI_V2_REPO_SETTINGS": true,
    "USERNAME_CONFIRMATION": false,
    "USER_CREATION": true,
    "USER_INITIALIZE": false,
    "USER_LAST_ACCESSED": true,
    "USER_LOG_ACCESS": false,
    "USER_METADATA": false,
    "USER_RENAME": false
  },
  "oauth": {
    "GITHUB_TRIGGER_CONFIG": {
      "AUTHORIZE_ENDPOINT": "https://github.com/login/oauth/authorize",
      "CLIENT_ID": null,
      "GITHUB_ENDPOINT": "https://github.com",
      "ORG_RESTRICT": false
    },
    "GITLAB_TRIGGER_CONFIG": {
      "AUTHORIZE_ENDPOINT": "https://gitlab.com/oauth/authorize",
      "CLIENT_ID": null,
      "GITLAB_ENDPOINT": "https://gitlab.com"
    }
  },
  "registry_state": "normal"
}

@michaelalang
Copy link
Contributor Author

michaelalang commented Sep 24, 2024

and here's the full output of the /api/v1/superuser/config endpoint

{
  "config": {
    "SESSION_COOKIE_SECURE": true,
    "PREFERRED_URL_SCHEME": "https",
    "ACTION_LOG_ARCHIVE_LOCATION": "local_us",
    "ACTION_LOG_ARCHIVE_PATH": "actionlogarchive/",
    "ACTION_LOG_AUDIT_LOGINS": true,
    "ACTION_LOG_ROTATION_THRESHOLD": "30d",
    "ALLOWED_OCI_ARTIFACT_TYPES": {
      "application/vnd.oci.image.config.v1+json": [
        "application/vnd.dev.cosign.simplesigning.v1+json",
        "application/vnd.dsse.envelope.v1+json",
        "text/spdx",
        "text/spdx+xml",
        "text/spdx+json",
        "application/vnd.syft+json",
        "application/vnd.cyclonedx",
        "application/vnd.cyclonedx+xml",
        "application/vnd.cyclonedx+json",
        "application/vnd.in-toto+json"
      ],
      "application/vnd.cncf.helm.config.v1+json": [
        "application/tar+gzip",
        "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
      ],
      "application/vnd.oci.source.image.config.v1+json": [
        "application/vnd.oci.image.layer.v1.tar+gzip"
      ],
      "application/vnd.unknown.config.v1+json": [
        "application/vnd.cncf.openpolicyagent.policy.layer.v1+rego",
        "application/vnd.cncf.openpolicyagent.data.layer.v1+json"
      ]
    },
    "ALLOW_PULLS_WITHOUT_STRICT_LOGGING": true,
    "AUTHENTICATION_TYPE": "LDAP",
    "AVATAR_KIND": "local",
    "BLACKLISTED_EMAIL_DOMAINS": [],
    "BLACKLIST_V2_SPEC": "<1.6.0",
    "BRANDING": {
      "logo": "/static/img/RH_Logo_Quay_Black_UX-horizontal.svg",
      "footer_img": "/static/img/RedHat.svg",
      "footer_url": "https://access.redhat.com/documentation/en-us/red_hat_quay/3/"
    },
    "BROWSER_API_CALLS_XHR_ONLY": false,
    "BUILDLOGS_REDIS": {
      "host": "redis",
      "password": "******** (8)",
      "port": 6379
    },
    "CLEAN_BLOB_UPLOAD_FOLDER": true,
    "CONTACT_INFO": [],
    "CORS_ORIGIN": "*",
    "CREATE_NAMESPACE_ON_PUSH": true,
    "CREATE_PRIVATE_REPO_ON_PUSH": true,
    "DB_CONNECTION_ARGS": {
      "max_connections": 30,
      "autorollback": true,
      "stale_timeout": 300,
      "keepalives": 1,
      "keepalives_idle": 10,
      "keepalives_interval": 2,
      "keepalives_count": 3,
      "tcp_user_timeout": 1000
    },
    "DB_URI": "********** (185)",
    "DEFAULT_SYSTEM_REJECT_QUOTA_BYTES": 0,
    "DEFAULT_TAG_EXPIRATION": "2w",
    "DIRECT_OAUTH_CLIENTID_WHITELIST": [],
    "DISTRIBUTED_STORAGE_CONFIG": {
      "default": [
        "RadosGWStorage",
        {
          "access_key": "********** (20)",
          "bucket_name": "quay-registry",
          "hostname": "s3.example.com",
          "is_secure": true,
          "port": 443,
          "secret_key": "********** (40)",
          "storage_path": "/datastorage/registry",
          "region_name": "us-east-1"
        }
      ]
    },
    "DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS": [
      "default"
    ],
    "DISTRIBUTED_STORAGE_PREFERENCE": [
      "default"
    ],
    "DOCUMENTATION_ROOT": "https://access.redhat.com/documentation/en-us/red_hat_quay/3/",
    "ENABLE_HEALTH_DEBUG_SECRET": "******** (8)",
    "ENTITLEMENT_RECONCILIATION_MARKETPLACE_ENDPOINT": "",
    "ENTITLEMENT_RECONCILIATION_USER_ENDPOINT": "",
    "EXPIRED_APP_SPECIFIC_TOKEN_GC": "1d",
    "EXPORT_COMPLIANCE_ENDPOINT": "",
    "EXTERNAL_TLS_TERMINATION": true,
    "FEATURE_ACTION_LOG_ROTATION": false,
    "FEATURE_ADVERTISE_V2": true,
    "FEATURE_AGGREGATED_LOG_COUNT_RETRIEVAL": true,
    "FEATURE_ANONYMOUS_ACCESS": true,
    "FEATURE_APP_SPECIFIC_TOKENS": true,
    "FEATURE_AUTO_PRUNE": false,
    "FEATURE_BITBUCKET_BUILD": false,
    "FEATURE_BLACKLISTED_EMAILS": false,
    "FEATURE_BUILD_SUPPORT": false,
    "FEATURE_CHANGE_TAG_EXPIRATION": true,
    "FEATURE_DIRECT_LOGIN": true,
    "FEATURE_ENTITLEMENT_RECONCILIATION": false,
    "FEATURE_EXPORT_COMPLIANCE": false,
    "FEATURE_EXTENDED_REPOSITORY_NAMES": true,
    "FEATURE_FIPS": false,
    "FEATURE_GARBAGE_COLLECTION": true,
    "FEATURE_GITHUB_BUILD": false,
    "FEATURE_GITHUB_LOGIN": false,
    "FEATURE_GITLAB_BUILD": false,
    "FEATURE_GOOGLE_LOGIN": false,
    "FEATURE_INVITE_ONLY_USER_CREATION": false,
    "FEATURE_LIBRARY_SUPPORT": true,
    "FEATURE_LOG_EXPORT": true,
    "FEATURE_MAILING": false,
    "FEATURE_NONSUPERUSER_TEAM_SYNCING_SETUP": false,
    "FEATURE_PARTIAL_USER_AUTOCOMPLETE": true,
    "FEATURE_PERMANENT_SESSIONS": true,
    "FEATURE_PROXY_CACHE": true,
    "FEATURE_PROXY_STORAGE": true,
    "FEATURE_PUBLIC_CATALOG": false,
    "FEATURE_QUOTA_MANAGEMENT": true,
    "FEATURE_RATE_LIMITS": false,
    "FEATURE_READER_BUILD_LOGS": false,
    "FEATURE_RECAPTCHA": false,
    "FEATURE_REPO_MIRROR": true,
    "FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH": false,
    "FEATURE_REQUIRE_TEAM_INVITE": true,
    "FEATURE_RESTRICTED_USERS": false,
    "FEATURE_RESTRICTED_V1_PUSH": true,
    "FEATURE_SECURITY_NOTIFICATIONS": true,
    "FEATURE_SECURITY_SCANNER": false,
    "FEATURE_SECURITY_SCANNER_NOTIFY_ON_NEW_INDEX": true,
    "FEATURE_STORAGE_REPLICATION": false,
    "FEATURE_SUPERUSERS_FULL_ACCESS": true,
    "FEATURE_SUPERUSERS_ORG_CREATION_ONLY": false,
    "FEATURE_SUPER_USERS": true,
    "FEATURE_TEAM_SYNCING": true,
    "FEATURE_UI_DELAY_AFTER_WRITE": false,
    "FEATURE_UI_V2": true,
    "FEATURE_UI_V2_REPO_SETTINGS": true,
    "FEATURE_USERNAME_CONFIRMATION": false,
    "FEATURE_USER_CREATION": true,
    "FEATURE_USER_INITIALIZE": false,
    "FEATURE_USER_LAST_ACCESSED": true,
    "FEATURE_USER_LOG_ACCESS": false,
    "FEATURE_USER_METADATA": false,
    "FEATURE_USER_RENAME": false,
    "FRESH_LOGIN_TIMEOUT": "10m",
    "GITHUB_LOGIN_CONFIG": {},
    "GITHUB_TRIGGER_CONFIG": {},
    "GLOBAL_READONLY_SUPER_USERS": [],
    "HEALTH_CHECKER": [
      "LDAPHealthCheck",
      {}
    ],
    "LOGS_MODEL": "database",
    "LOGS_MODEL_CONFIG": {
      "should_skip_logging": null
    },
    "LOG_ARCHIVE_LOCATION": "default",
    "LOG_ARCHIVE_PATH": "logarchive/",
    "MAIL_DEFAULT_SENDER": "support@quay.io",
    "MAIL_PORT": 587,
    "MAIL_SERVER": "",
    "MAIL_USE_TLS": false,
    "MAXIMUM_LAYER_SIZE": "20G",
    "PERMANENTLY_DELETE_TAGS": false,
    "PROMETHEUS_NAMESPACE": "quay",
    "PUBLIC_NAMESPACES": [],
    "QUOTA_BACKFILL": true,
    "QUOTA_INVALIDATE_TOTALS": true,
    "QUOTA_TOTAL_DELAY_SECONDS": 1800,
    "RECAPTCHA_WHITELISTED_USERS": [],
    "REGISTRY_STATE": "normal",
    "REGISTRY_TITLE": "Example Container Registry",
    "REGISTRY_TITLE_SHORT": "Example",
    "REPO_MIRROR_INTERVAL": 30,
    "REPO_MIRROR_ROLLBACK": false,
    "REPO_MIRROR_TLS_VERIFY": true,
    "RESET_CHILD_MANIFEST_EXPIRATION": false,
    "ROBOTS_WHITELIST": [],
    "SEARCH_MAX_RESULT_PAGE_COUNT": 10,
    "SEARCH_RESULTS_PER_PAGE": 10,
    "SECURITY_SCANNER_INDEXING_INTERVAL": 30,
    "SECURITY_SCANNER_V4_ENDPOINT": "https://clair.example.com",
    "SECURITY_SCANNER_V4_MANIFEST_CLEANUP": true,
    "SECURITY_SCANNER_V4_PSK": "********** (20)",
    "SERVER_HOSTNAME": "quay.example.com",
    "SUCCESSIVE_TRIGGER_FAILURE_DISABLE_THRESHOLD": 100,
    "SUCCESSIVE_TRIGGER_INTERNAL_ERROR_DISABLE_THRESHOLD": 5,
    "SUPER_USERS": [],
    "TAG_EXPIRATION_OPTIONS": [
      "0s",
      "1d",
      "1w",
      "2w",
      "4w"
    ],
    "TEAM_RESYNC_STALE_TIME": "6h",
    "TERMS_OF_SERVICE_URL": "",
    "UI_DELAY_AFTER_WRITE_SECONDS": 3,
    "UI_V2_FEEDBACK_FORM": "https://7qdvkuo9rkj.typeform.com/to/XH5YE79P",
    "USERFILES_LOCATION": "default",
    "USERFILES_PATH": "userfiles/",
    "USER_EVENTS_REDIS": {
      "host": "redis",
      "password": "******** (8)",
      "port": 6379
    },
    "USER_RECOVERY_TOKEN_LIFETIME": "30m",
    "V1_PUSH_WHITELIST": [],
    "V2_PAGINATION_SIZE": 50,
    "WEBHOOK_HOSTNAME_BLACKLIST": []
  },
  "warning": {
    "DEBUG": false,
    "TESTING": false,
    "PROPAGATE_EXCEPTIONS": true,
    "SECRET_KEY": "********** (36)",
    "USE_X_SENDFILE": false,
    "APPLICATION_ROOT": "/",
    "SESSION_COOKIE_NAME": "_csrf_token",
    "SESSION_COOKIE_HTTPONLY": true,
    "SESSION_COOKIE_SAMESITE": "Lax",
    "SESSION_REFRESH_EACH_REQUEST": true,
    "SEND_FILE_MAX_AGE_DEFAULT": 0,
    "TRAP_HTTP_EXCEPTIONS": false,
    "EXPLAIN_TEMPLATE_LOADING": false,
    "MAX_COOKIE_SIZE": 4093,
    "ACCOUNT_RECOVERY_MODE": false,
    "ANALYTICS_TYPE": "FakeAnalytics",
    "AVATAR_COLORS": [
      "#969696",
      "#aec7e8",
      "#ff7f0e",
      "#ffbb78",
      "#2ca02c",
      "#98df8a",
      "#d62728",
      "#ff9896",
      "#9467bd",
      "#c5b0d5",
      "#8c564b",
      "#c49c94",
      "#e377c2",
      "#f7b6d2",
      "#7f7f7f",
      "#c7c7c7",
      "#bcbd22",
      "#1f77b4",
      "#17becf",
      "#9edae5",
      "#393b79",
      "#5254a3",
      "#6b6ecf",
      "#9c9ede",
      "#9ecae1",
      "#31a354",
      "#b5cf6b",
      "#a1d99b",
      "#8c6d31",
      "#ad494a",
      "#e7ba52",
      "#a55194"
    ],
    "BILLING_TYPE": "FakeStripe",
    "BUILDLOGS_OPTIONS": [],
    "BUILD_MANAGER": null,
    "CHANNEL_COLORS": [
      "#969696",
      "#aec7e8",
      "#ff7f0e",
      "#ffbb78",
      "#2ca02c",
      "#98df8a",
      "#d62728",
      "#ff9896",
      "#9467bd",
      "#c5b0d5",
      "#8c564b",
      "#c49c94",
      "#e377c2",
      "#f7b6d2",
      "#7f7f7f",
      "#c7c7c7",
      "#bcbd22",
      "#1f77b4",
      "#17becf",
      "#9edae5",
      "#393b79",
      "#5254a3",
      "#6b6ecf",
      "#9c9ede",
      "#9ecae1",
      "#31a354",
      "#b5cf6b",
      "#a1d99b",
      "#8c6d31",
      "#ad494a",
      "#e7ba52",
      "#a55194"
    ],
    "CHUNK_CLEANUP_QUEUE_NAME": "chunk_cleanup",
    "DATA_MODEL_CACHE_CONFIG": {
      "engine": "memcached",
      "endpoint": null,
      "repository_blob_cache_ttl": "60s",
      "catalog_page_cache_ttl": "60s",
      "namespace_geo_restrictions_cache_ttl": "240s",
      "active_repo_tags_cache_ttl": "120s"
    },
    "DEFAULT_LABEL_KEY_RESERVED_PREFIXES": [
      "com.docker.",
      "io.docker.",
      "org.dockerproject.",
      "org.opencontainers.",
      "io.cncf.",
      "io.kubernetes.",
      "io.k8s.",
      "io.quay",
      "com.coreos",
      "com.tectonic",
      "internal",
      "quay"
    ],
    "DISABLED_FOR_AUDIT_LOGS": [],
    "DISABLED_FOR_PULL_LOGS": [],
    "DOCKERFILE_BUILD_QUEUE_NAME": "dockerfilebuild",
    "EXCEPTION_LOG_TYPE": "FakeSentry",
    "EXPIRED_SERVICE_KEY_TTL_SEC": 604800,
    "EXPORT_ACTION_LOGS_QUEUE_NAME": "exportactionlogs",
    "FEATURE_BILLING": false,
    "FEATURE_CLEAR_EXPIRED_RAC_ENTRIES": false,
    "FEATURE_DISABLE_PULL_LOGS_FOR_FREE_NAMESPACES": false,
    "FEATURE_GENERAL_OCI_SUPPORT": true,
    "FEATURE_HELM_OCI_SUPPORT": true,
    "FEATURE_MANIFEST_SIZE_BACKFILL": true,
    "FEATURE_NAMESPACE_GARBAGE_COLLECTION": true,
    "FEATURE_REPOSITORY_ACTION_COUNTER": true,
    "FEATURE_REPOSITORY_GARBAGE_COLLECTION": true,
    "FEATURE_RH_MARKETPLACE": false,
    "FEATURE_SIGNING": false,
    "GARBAGE_COLLECTION_FREQUENCY": 30,
    "GREENLET_TRACING": true,
    "INSTANCE_SERVICE_KEY_EXPIRATION": 120,
    "INSTANCE_SERVICE_KEY_KID_LOCATION": "/quay-registry/conf/quay.kid",
    "INSTANCE_SERVICE_KEY_LOCATION": "/quay-registry/conf/quay.pem",
    "INSTANCE_SERVICE_KEY_REFRESH": 55,
    "INSTANCE_SERVICE_KEY_SERVICE": "quay",
    "JSONIFY_PRETTYPRINT_REGULAR": false,
    "LABEL_KEY_RESERVED_PREFIXES": [],
    "LAST_ACCESSED_UPDATE_THRESHOLD_S": 60,
    "LIBRARY_NAMESPACE": "library",
    "LOCAL_OAUTH_HANDLER": "/oauth/localapp",
    "LOGGING_LEVEL": "DEBUG",
    "MAIL_FAIL_SILENTLY": false,
    "NAMESPACE_GC_QUEUE_NAME": "namespacegc",
    "NOTIFICATION_QUEUE_NAME": "notification",
    "NOTIFICATION_SEND_TIMEOUT": 10,
    "PAGE_TOKEN_KEY": "********** (44)",
    "PROMETHEUS_PUSHGATEWAY_URL": "http://localhost:9091",
    "PUSH_TEMP_TAG_EXPIRATION_SEC": 3600,
    "QUEUE_METRICS_TYPE": "Null",
    "QUEUE_WORKER_METRICS_REFRESH_SECONDS": 30,
    "REGISTRY_JWT_AUTH_MAX_FRESH_S": 3660,
    "REPLICATION_QUEUE_NAME": "imagestoragereplication",
    "REPOSITORY_GC_QUEUE_NAME": "repositorygc",
    "SECSCAN_V4_NOTIFICATION_QUEUE_NAME": "secscanv4",
    "SECURITY_SCANNER_V4_REINDEX_THRESHOLD": 300,
    "SIGNED_GRANT_EXPIRATION_SEC": 86400,
    "STAGGER_WORKERS": true,
    "STATUS_TAGS": {
      "building": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"117\" height=\"20\"><linearGradient id=\"b\" x2=\"0\" y2=\"100%\"><stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/><stop offset=\"1\" stop-opacity=\".1\"/></linearGradient><mask id=\"a\"><rect width=\"117\" height=\"20\" rx=\"3\" fill=\"#fff\"/></mask><g mask=\"url("#a")\"><path fill=\"#555\" d=\"M0 0h63v20H0z\"/><path fill=\"#dfb317\" d=\"M63 0h54v20H63z\"/><path fill=\"url("#b")\" d=\"M0 0h117v20H0z\"/></g><g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\"><text x=\"32.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">container</text><text x=\"32.5\" y=\"14\">container</text><text x=\"89\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">building</text><text x=\"89\" y=\"14\">building</text></g></svg>",
      "failed": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"104\" height=\"20\"><linearGradient id=\"b\" x2=\"0\" y2=\"100%\"><stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/><stop offset=\"1\" stop-opacity=\".1\"/></linearGradient><mask id=\"a\"><rect width=\"104\" height=\"20\" rx=\"3\" fill=\"#fff\"/></mask><g mask=\"url("#a")\"><path fill=\"#555\" d=\"M0 0h63v20H0z\"/><path fill=\"#e05d44\" d=\"M63 0h41v20H63z\"/><path fill=\"url("#b")\" d=\"M0 0h104v20H0z\"/></g><g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\"><text x=\"32.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">container</text><text x=\"32.5\" y=\"14\">container</text><text x=\"82.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">failed</text><text x=\"82.5\" y=\"14\">failed</text></g></svg>",
      "none": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"101\" height=\"20\"><linearGradient id=\"b\" x2=\"0\" y2=\"100%\"><stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/><stop offset=\"1\" stop-opacity=\".1\"/></linearGradient><mask id=\"a\"><rect width=\"101\" height=\"20\" rx=\"3\" fill=\"#fff\"/></mask><g mask=\"url("#a")\"><path fill=\"#555\" d=\"M0 0h63v20H0z\"/><path fill=\"#9f9f9f\" d=\"M63 0h38v20H63z\"/><path fill=\"url("#b")\" d=\"M0 0h101v20H0z\"/></g><g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\"><text x=\"32.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">container</text><text x=\"32.5\" y=\"14\">container</text><text x=\"81\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">none</text><text x=\"81\" y=\"14\">none</text></g></svg>",
      "ready": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"106\" height=\"20\"><linearGradient id=\"b\" x2=\"0\" y2=\"100%\"><stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/><stop offset=\"1\" stop-opacity=\".1\"/></linearGradient><mask id=\"a\"><rect width=\"106\" height=\"20\" rx=\"3\" fill=\"#fff\"/></mask><g mask=\"url("#a")\"><path fill=\"#555\" d=\"M0 0h63v20H0z\"/><path fill=\"#97CA00\" d=\"M63 0h43v20H63z\"/><path fill=\"url("#b")\" d=\"M0 0h106v20H0z\"/></g><g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\"><text x=\"32.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">container</text><text x=\"32.5\" y=\"14\">container</text><text x=\"83.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">ready</text><text x=\"83.5\" y=\"14\">ready</text></g></svg>",
      "cancelled": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"123\" height=\"20\"><linearGradient id=\"b\" x2=\"0\" y2=\"100%\"><stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/><stop offset=\"1\" stop-opacity=\".1\"/></linearGradient><mask id=\"a\"><rect width=\"123\" height=\"20\" rx=\"3\" fill=\"#fff\"/></mask><g mask=\"url("#a")\"><path fill=\"#555\" d=\"M0 0h63v20H0z\"/><path fill=\"#9f9f9f\" d=\"M63 0h60v20H63z\"/><path fill=\"url("#b")\" d=\"M0 0h123v20H0z\"/></g><g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\"><text x=\"32.5\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">container</text><text x=\"32.5\" y=\"14\">container</text><text x=\"92\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">cancelled</text><text x=\"92\" y=\"14\">cancelled</text></g></svg>\n"
    },
    "TEAM_SYNC_WORKER_FREQUENCY": 60,
    "UNAPPROVED_SERVICE_KEY_TTL_SEC": 86400,
    "USE_CDN": false,
    "DATABASE_SECRET_KEY": "********** (36)",
    "DB_CONNECTION_POOLING": false,
    "FEATURE_ACI_CONVERSION": false,
    "FEATURE_APP_REGISTRY": false,
    "GITLAB_TRIGGER_KIND": {},
    "MAIL_USE_AUTH": false,
    "SETUP_COMPLETE": true,
    "LDAP_ADMIN_DN": "cn=quay,ou=people,dc=example,dc=com",
    "LDAP_ADMIN_PASSWD": "********** (36)",
    "LDAP_ALLOW_INSECURE_FALLBACK": false,
    "LDAP_BASE_DN": [
      "dc=example",
      "dc=com"
    ],
    "LDAP_EMAIL_ATTR": "mail",
    "LDAP_SECONDARY_USER_RDNS": [
      "ou=People,ou=lab"
    ],
    "LDAP_UID_ATTR": "uid",
    "LDAP_URI": "ldaps://ldap.example.com:636",
    "LDAP_USER_RDN": [
      "ou=people"
    ],
    "LDAP_SUPERUSER_FILTER": "(|(memberOf=cn=quay-superusers,ou=Groups,dc=example,dc=com)(memberOf=cn=quay-readonly-superuser,ou=Groups,dc=example,dc=com))",
    "LDAP_RESTRICTED_USER_FILTER": "(&(memberOf=cn=allusers,ou=Groups,dc=example,dc=com)(!(memberOf=cn=quay-superusers,ou=Groups,dc=example,dc=com)))",
    "LDAP_FOLLOW_REFERRALS": 0,
    "LDAP_POOL": 50,
    "GLOBAL_PROMETHEUS_STATS_FREQUENCY": 60,
    "HOME_LOGIN_CONFIG": {
      "CLIENT_ID": "Example",
      "CLIENT_SECRET": "********** (32)",
      "OIDC_SERVER": "https://sso.apps.example.com/realms/Example/",
      "SERVICE_NAME": "Example",
      "LOGIN_BINDING_FIELD": "username"
    },
    "FEATURE_FULL_CONFIG_SHOW": true,
    "INVALID_NOT_WORKING": true
  },
  "env": {
    "REDIS_PORT_6379_TCP_PROTO": "tcp",
    "PYTHONUNBUFFERED": "1",
    "LC_ALL": "C.UTF-8",
    "REDIS_PORT": "tcp://172.31.223.51:6379",
    "IGNORE_VALIDATION": "True",
    "WORKER_COUNT_SECSCAN": "1",
    "QUAY_PORT_8080_TCP_PORT": "8080",
    "DB_CONNECTION_POOLING": "true",
    "LANG": "C.UTF-8",
    "TZ": "UTC",
    "WORKER_COUNT_REGISTRY": "10",
    "HOSTNAME": "quay-5bf8bd7575-hfqh2",
    "QUAYDIR": "/quay-registry",
    "REDIS_PORT_6379_TCP_ADDR": "172.31.223.51",
    "QUAYCONF": "/quay-registry/conf",
    "REDIS_PORT_6379_TCP": "tcp://172.31.223.51:6379",
    "DB_CONNECTION_POOLING_REGISTRY": "true",
    "QUAY_SERVICE_HOST": "172.31.109.228",
    "QUAY_PORT_9091_TCP_PROTO": "tcp",
    "REDIS_SERVICE_PORT": "6379",
    "PYTHONIOENCODING": "UTF-8",
    "KUBERNETES_PORT_443_TCP_PROTO": "tcp",
    "KUBERNETES_PORT_443_TCP_ADDR": "172.31.0.1",
    "container": "oci",
    "QUAYPATH": "/quay-registry",
    "QUAY_PORT_9091_TCP_ADDR": "172.31.109.228",
    "USERS_DEBUG": "0",
    "QUAY_CONFIG_READ_ONLY_FIELD_GROUPS": "\"\"",
    "KUBERNETES_PORT": "tcp://172.31.0.1:443",
    "QUAY_SERVICE_PORT_HTTP_8080": "8080",
    "QUAY_PORT_8080_TCP": "tcp://172.31.109.228:8080",
    "PWD": "/quay-registry",
    "QUAY_PORT_8080_TCP_ADDR": "172.31.109.228",
    "QUAY_PORT_8080_TCP_PROTO": "tcp",
    "HOME": "/",
    "QUAY_SERVICE_PORT_HTTP_9091": "9091",
    "REDIS_SERVICE_PORT_REDIS": "6379",
    "RED_HAT_QUAY": "true",
    "KUBERNETES_SERVICE_PORT_HTTPS": "443",
    "REDIS_SERVICE_HOST": "172.31.223.51",
    "KUBERNETES_PORT_443_TCP_PORT": "443",
    "KUBERNETES_PORT_443_TCP": "tcp://172.31.0.1:443",
    "REDIS_PORT_6379_TCP_PORT": "6379",
    "QUAY_SERVICE_PORT": "8080",
    "TERM": "xterm",
    "QUAY_PORT_9091_TCP": "tcp://172.31.109.228:9091",
    "NSS_SDB_USE_CACHE": "no",
    "OPERATOR_ENDPOINT": "\"\"",
    "QUAY_PORT": "tcp://172.31.109.228:8080",
    "SHLVL": "0",
    "PYTHONPATH": "/quay-registry",
    "DEBUGLOG": "true",
    "KUBERNETES_SERVICE_PORT": "443",
    "PYTHONUSERBASE": "/app",
    "PATH": "/app/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
    "CONFIG_APP_PASSWORD": "\"\"",
    "KUBERNETES_SERVICE_HOST": "172.31.0.1",
    "QUAY_PORT_9091_TCP_PORT": "9091",
    "QUAYRUN": "/quay-registry/conf",
    "SUPERVISOR_ENABLED": "1",
    "SUPERVISOR_SERVER_URL": "unix:///quay-registry/conf/supervisord.sock",
    "SUPERVISOR_PROCESS_NAME": "gunicorn-web",
    "SUPERVISOR_GROUP_NAME": "gunicorn-web",
    "SERVER_SOFTWARE": "gunicorn/21.2.0"
  },
  "schema": {
    "type": "object",
    "description": "Schema for Quay configuration",
    "required": [
      "PREFERRED_URL_SCHEME",
      "SERVER_HOSTNAME",
      "DB_URI",
      "AUTHENTICATION_TYPE",
      "DISTRIBUTED_STORAGE_CONFIG",
      "BUILDLOGS_REDIS",
      "USER_EVENTS_REDIS",
      "DISTRIBUTED_STORAGE_PREFERENCE",
      "DEFAULT_TAG_EXPIRATION",
      "TAG_EXPIRATION_OPTIONS"
    ],
    "properties": {
      "REGISTRY_STATE": {
        "type": "string",
        "description": "The state of the registry.",
        "enum": [
          "normal",
          "readonly"
        ],
        "x-example": "readonly"
      },
      "PREFERRED_URL_SCHEME": {
        "type": "string",
        "description": "The URL scheme to use when hitting Quay. If Quay is behind SSL *at all*, this *must* be `https`",
        "enum": [
          "http",
          "https"
        ],
        "x-example": "https"
      },
      "SERVER_HOSTNAME": {
        "type": "string",
        "description": "The URL at which Quay is accessible, without the scheme.",
        "x-example": "quay.io"
      },
      "EXTERNAL_TLS_TERMINATION": {
        "type": "boolean",
        "description": "If TLS is supported, but terminated at a layer before Quay, must be true.",
        "x-example": true
      },
      "SSL_CIPHERS": {
        "type": "array",
        "description": "If specified, the nginx-defined list of SSL ciphers to enabled and disabled",
        "x-example": [
          "CAMELLIA",
          "!3DES"
        ],
        "x-reference": "http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers"
      },
      "SSL_PROTOCOLS": {
        "type": "array",
        "description": "If specified, the nginx-defined list of SSL protocols to enabled and disabled",
        "x-example": [
          "TLSv1.1",
          "TLSv1.2"
        ],
        "x-reference": "http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols"
      },
      "REGISTRY_TITLE": {
        "type": "string",
        "description": "If specified, the long-form title for the registry. Defaults to `Red Hat Quay`.",
        "x-example": "Corp Container Service"
      },
      "REGISTRY_TITLE_SHORT": {
        "type": "string",
        "description": "If specified, the short-form title for the registry. Defaults to `Red Hat Quay`.",
        "x-example": "CCS"
      },
      "CONTACT_INFO": {
        "type": "array",
        "uniqueItems": true,
        "description": "If specified, contact information to display on the contact page. If only a single piece of contact information is specified, the contact footer will link directly.",
        "items": [
          {
            "type": "string",
            "pattern": "^mailto:(.)+$",
            "x-example": "mailto:admin@example.com",
            "description": "Adds a link to send an e-mail"
          },
          {
            "type": "string",
            "pattern": "^irc://(.)+$",
            "x-example": "irc://irc.libera.chat:6667/quay",
            "description": "Adds a link to visit an IRC chat room"
          },
          {
            "type": "string",
            "pattern": "^tel:(.)+$",
            "x-example": "tel:+1-888-930-3475",
            "description": "Adds a link to call a phone number"
          },
          {
            "type": "string",
            "pattern": "^http(s)?://(.)+$",
            "x-example": "https://twitter.com/quayio",
            "description": "Adds a link to a defined URL"
          }
        ]
      },
      "SEARCH_RESULTS_PER_PAGE": {
        "type": "number",
        "description": "Number of results returned per page by search page. Defaults to 10",
        "x-example": 10
      },
      "SEARCH_MAX_RESULT_PAGE_COUNT": {
        "type": "number",
        "description": "Maximum number of pages the user can paginate in search before they are limited. Defaults to 10",
        "x-example": 10
      },
      "FEATURE_MAILING": {
        "type": "boolean",
        "description": "Whether emails are enabled. Defaults to True",
        "x-example": true
      },
      "MAIL_SERVER": {
        "type": "string",
        "description": "The SMTP server to use for sending e-mails. Only required if FEATURE_MAILING is set to true.",
        "x-example": "smtp.somedomain.com"
      },
      "MAIL_USE_TLS": {
        "type": "boolean",
        "description": "If specified, whether to use TLS for sending e-mails.",
        "x-example": true
      },
      "MAIL_PORT": {
        "type": "number",
        "description": "The SMTP port to use. If not specified, defaults to 587.",
        "x-example": 588
      },
      "MAIL_USERNAME": {
        "type": [
          "string",
          "null"
        ],
        "description": "The SMTP username to use when sending e-mails.",
        "x-example": "myuser"
      },
      "MAIL_PASSWORD": {
        "type": [
          "string",
          "null"
        ],
        "description": "The SMTP password to use when sending e-mails.",
        "x-example": "mypassword"
      },
      "MAIL_DEFAULT_SENDER": {
        "type": [
          "string",
          "null"
        ],
        "description": "If specified, the e-mail address used as the `from` when Quay sends e-mails. If none, defaults to `admin@example.com`.",
        "x-example": "support@myco.com"
      },
      "DB_URI": {
        "type": "string",
        "description": "The URI at which to access the database, including any credentials.",
        "x-example": "mysql+pymysql://username:password@dns.of.database/quay",
        "x-reference": "https://www.postgresql.org/docs/9.3/static/libpq-connect.html#AEN39495"
      },
      "DB_CONNECTION_ARGS": {
        "type": "object",
        "description": "If specified, connection arguments for the database such as timeouts and SSL.",
        "properties": {
          "threadlocals": {
            "type": "boolean",
            "description": "Whether to use thread-local connections. Should *ALWAYS* be `true`"
          },
          "autorollback": {
            "type": "boolean",
            "description": "Whether to use auto-rollback connections. Should *ALWAYS* be `true`"
          },
          "ssl": {
            "type": "object",
            "description": "SSL connection configuration",
            "properties": {
              "ca": {
                "type": "string",
                "description": "*Absolute container path* to the CA certificate to use for SSL connections",
                "x-example": "conf/stack/ssl-ca-cert.pem"
              }
            },
            "required": [
              "ca"
            ]
          }
        },
        "required": [
          "threadlocals",
          "autorollback"
        ]
      },
      "ALLOW_PULLS_WITHOUT_STRICT_LOGGING": {
        "type": "boolean",
        "description": "If true, pulls in which the pull audit log entry cannot be written will still succeed. Useful if the database can fallback into a read-only state and it is desired for pulls to continue during that time. Defaults to False.",
        "x-example": true
      },
      "FEATURE_STORAGE_REPLICATION": {
        "type": "boolean",
        "description": "Whether to automatically replicate between storage engines. Defaults to False",
        "x-example": false
      },
      "FEATURE_PROXY_STORAGE": {
        "type": "boolean",
        "description": "Whether to proxy all direct download URLs in storage via the registry nginx. Defaults to False",
        "x-example": false
      },
      "FEATURE_PROXY_CACHE": {
        "type": "boolean",
        "description": "Whether pull through proxy cache feature is enabled. Defaults to False",
        "x-example": false
      },
      "MAXIMUM_LAYER_SIZE": {
        "type": "string",
        "description": "Maximum allowed size of an image layer. Defaults to 20G",
        "x-example": "100G",
        "pattern": "^[0-9]+(G|M)$"
      },
      "DISTRIBUTED_STORAGE_CONFIG": {
        "type": "object",
        "description": "Configuration for storage engine(s) to use in Quay. Each key is a unique ID for a storage engine, with the value being a tuple of the type and  configuration for that engine.",
        "x-example": {
          "local_storage": [
            "LocalStorage",
            {
              "storage_path": "some/path/"
            }
          ]
        },
        "items": {
          "type": "array"
        }
      },
      "DISTRIBUTED_STORAGE_PREFERENCE": {
        "type": "array",
        "description": "The preferred storage engine(s) (by ID in DISTRIBUTED_STORAGE_CONFIG) to use. A preferred engine means it is first checked for pullig and images are pushed to it.",
        "items": {
          "type": "string",
          "uniqueItems": true
        },
        "x-example": [
          "s3_us_east",
          "s3_us_west"
        ]
      },
      "DISTRIBUTED_STORAGE_DEFAULT_LOCATIONS": {
        "type": "array",
        "description": "The list of storage engine(s) (by ID in DISTRIBUTED_STORAGE_CONFIG) whose images should be fully replicated, by default, to all other storage engines.",
        "items": {
          "type": "string",
          "uniqueItems": true
        },
        "x-example": [
          "s3_us_east",
          "s3_us_west"
        ]
      },
      "USERFILES_LOCATION": {
        "type": "string",
        "description": "ID of the storage engine in which to place user-uploaded files",
        "x-example": "s3_us_east"
      },
      "USERFILES_PATH": {
        "type": "string",
        "description": "Path under storage in which to place user-uploaded files",
        "x-example": "userfiles"
      },
      "ACTION_LOG_AUDIT_LOGINS": {
        "type": "string",
        "description": "Whether to log all registry API and Quay API/UI logins event to the action log. Defaults to True",
        "x-example": false
      },
      "ACTION_LOG_ARCHIVE_LOCATION": {
        "type": "string",
        "description": "If action log archiving is enabled, the storage engine in which to place the archived data.",
        "x-example": "s3_us_east"
      },
      "ACTION_LOG_ARCHIVE_PATH": {
        "type": "string",
        "description": "If action log archiving is enabled, the path in storage in which to place the archived data.",
        "x-example": "archives/actionlogs"
      },
      "ACTION_LOG_ROTATION_THRESHOLD": {
        "type": "string",
        "description": "If action log archiving is enabled, the time interval after which to archive data.",
        "x-example": "30d"
      },
      "LOG_ARCHIVE_LOCATION": {
        "type": "string",
        "description": "If builds are enabled, the storage engine in which to place the archived build logs.",
        "x-example": "s3_us_east"
      },
      "LOG_ARCHIVE_PATH": {
        "type": "string",
        "description": "If builds are enabled, the path in storage in which to place the archived build logs.",
        "x-example": "archives/buildlogs"
      },
      "AUTHENTICATION_TYPE": {
        "type": "string",
        "description": "The authentication engine to use for credential authentication.",
        "x-example": "Database",
        "enum": [
          "Database",
          "LDAP",
          "JWT",
          "Keystone",
          "OIDC",
          "AppToken"
        ]
      },
      "SUPER_USERS": {
        "type": "array",
        "description": "Quay usernames of those users to be granted superuser privileges",
        "uniqueItems": true,
        "items": {
          "type": "string"
        }
      },
      "DIRECT_OAUTH_CLIENTID_WHITELIST": {
        "type": "array",
        "description": "A list of client IDs of *Quay-managed* applications that are allowed to perform direct OAuth approval without user approval.",
        "x-reference": "https://coreos.com/quay-enterprise/docs/latest/direct-oauth.html",
        "uniqueItems": true,
        "items": {
          "type": "string"
        }
      },
      "BUILDLOGS_REDIS": {
        "type": "object",
        "description": "Connection information for Redis for build logs caching",
        "required": [
          "host"
        ],
        "properties": {
          "host": {
            "type": "string",
            "description": "The hostname at which Redis is accessible",
            "x-example": "my.redis.cluster"
          },
          "port": {
            "type": "number",
            "description": "The port at which Redis is accessible",
            "x-example": 1234
          },
          "password": {
            "type": "string",
            "description": "The password to connect to the Redis instance",
            "x-example": "mypassword"
          }
        }
      },
      "USER_EVENTS_REDIS": {
        "type": "object",
        "description": "Connection information for Redis for user event handling",
        "required": [
          "host"
        ],
        "properties": {
          "host": {
            "type": "string",
            "description": "The hostname at which Redis is accessible",
            "x-example": "my.redis.cluster"
          },
          "port": {
            "type": "number",
            "description": "The port at which Redis is accessible",
            "x-example": 1234
          },
          "password": {
            "type": "string",
            "description": "The password to connect to the Redis instance",
            "x-example": "mypassword"
          }
        }
      },
      "GITHUB_LOGIN_CONFIG": {
        "type": [
          "object",
          "null"
        ],
        "description": "Configuration for using GitHub (Enterprise) as an external login provider",
        "required": [
          "CLIENT_ID",
          "CLIENT_SECRET"
        ],
        "x-reference": "https://coreos.com/quay-enterprise/docs/latest/github-auth.html",
        "properties": {
          "GITHUB_ENDPOINT": {
            "type": "string",
            "description": "The endpoint of the GitHub (Enterprise) being hit",
            "x-example": "https://github.com/"
          },
          "API_ENDPOINT": {
            "type": "string",
            "description": "The endpoint of the GitHub (Enterprise) API to use. Must be overridden for github.com",
            "x-example": "https://api.github.com/"
          },
          "CLIENT_ID": {
            "type": "string",
            "description": "The registered client ID for this Quay instance; cannot be shared with GITHUB_TRIGGER_CONFIG",
            "x-example": "0e8dbe15c4c7630b6780",
            "x-reference": "https://coreos.com/quay-enterprise/docs/latest/github-app.html"
          },
          "CLIENT_SECRET": {
            "type": "string",
            "description": "The registered client secret for this Quay instance",
            "x-example": "e4a58ddd3d7408b7aec109e85564a0d153d3e846",
            "x-reference": "https://coreos.com/quay-enterprise/docs/latest/github-app.html"
          },
          "ORG_RESTRICT": {
            "type": "boolean",
            "description": "If true, only users within the organization whitelist can login using this provider",
            "x-example": true
          },
          "ALLOWED_ORGANIZATIONS": {
            "type": "array",
            "description": "The names of the GitHub (Enterprise) organizations whitelisted to work with the ORG_RESTRICT option",
            "uniqueItems": true,
            "items": {
              "type": "string"
            }
          }
        }
      },
      "BITBUCKET_TRIGGER_CONFIG": {
        "type": [
          "object",
          "null"
        ],
        "description": "Configuration for using BitBucket for build triggers",
        "required": [
          "CONSUMER_KEY",
          "CONSUMER_SECRET"
        ],
        "x-reference": "https://coreos.com/quay-enterprise/docs/latest/bitbucket-build.html",
        "properties": {
          "CONSUMER_KEY": {
            "type": "string",
            "description": "The registered consumer key (client ID) for this Quay instance",
            "x-example": "0e8dbe15c4c7630b6780"
          },
          "CONSUMER_SECRET": {
            "type": "string",
            "description": "The registered consumer secret (client secret) for this Quay instance",
            "x-example": "e4a58ddd3d7408b7aec109e85564a0d153d3e846"
          }
        }
      },
      "GITHUB_TRIGGER_CONFIG": {
        "type": [
          "object",
          "null"
        ],
        "description": "Configuration for using GitHub (Enterprise) for build triggers",
        "required": [
          "GITHUB_ENDPOINT",
          "CLIENT_ID",
          "CLIENT_SECRET"
        ],
        "x-reference": "https://coreos.com/quay-enterprise/docs/latest/github-build.html",
        "properties": {
          "GITHUB_ENDPOINT": {
            "type": "string",
            "description": "The endpoint of the GitHub (Enterprise) being hit",
            "x-example": "https://github.com/"
          },
          "API_ENDPOINT": {
            "type": "string",
            "description": "The endpoint of the GitHub (Enterprise) API to use. Must be overridden for github.com",
            "x-example": "https://api.github.com/"
          },
          "CLIENT_ID": {
            "type": "string",
            "description": "The registered client ID for this Quay instance; cannot be shared with GITHUB_LOGIN_CONFIG",
            "x-example": "0e8dbe15c4c7630b6780",
            "x-reference": "https://coreos.com/quay-enterprise/docs/latest/github-app.html"
          },
          "CLIENT_SECRET": {
            "type": "string",
            "description": "The registered client secret for this Quay instance",
            "x-example": "e4a58ddd3d7408b7aec109e85564a0d153d3e846",
            "x-reference": "https://coreos.com/quay-enterprise/docs/latest/github-app.html"
          }
        }
      },
      "GOOGLE_LOGIN_CONFIG": {
        "type": [
          "object",
          "null"
        ],
        "description": "Configuration for using Google for external authentication",
        "required": [
          "CLIENT_ID",
          "CLIENT_SECRET"
        ],
        "properties": {
          "CLIENT_ID": {
            "type": "string",
            "description": "The registered client ID for this Quay instance",
            "x-example": "0e8dbe15c4c7630b6780"
          },
          "CLIENT_SECRET": {
            "type": "string",
            "description": "The registered client secret for this Quay instance",
            "x-example": "e4a58ddd3d7408b7aec109e85564a0d153d3e846"
          }
        }
      },
      "GITLAB_TRIGGER_CONFIG": {
        "type": [
          "object",
          "null"
        ],
        "description": "Configuration for using Gitlab (Enterprise) for external authentication",
        "required": [
          "GITLAB_ENDPOINT",
          "CLIENT_ID",
          "CLIENT_SECRET"
        ],
        "properties": {
          "GITLAB_ENDPOINT": {
            "type": "string",
            "description": "The endpoint at which Gitlab(Enterprise) is running",
            "x-example": "https://gitlab.com"
          },
          "CLIENT_ID": {
            "type": "string",
            "description": "The registered client ID for this Quay instance",
            "x-example": "0e8dbe15c4c7630b6780"
          },
          "CLIENT_SECRET": {
            "type": "string",
            "description": "The registered client secret for this Quay instance",
            "x-example": "e4a58ddd3d7408b7aec109e85564a0d153d3e846"
          }
        }
      },
      "BRANDING": {
        "type": [
          "object",
          "null"
        ],
        "description": "Custom branding for logos and URLs in the Quay UI",
        "required": [
          "logo"
        ],
        "properties": {
          "logo": {
            "type": "string",
            "description": "Main logo image URL",
            "x-example": "/static/img/quay-horizontal-color.svg"
          },
          "footer_img": {
            "type": "string",
            "description": "Logo for UI footer",
            "x-example": "/static/img/RedHat.svg"
          },
          "footer_url": {
            "type": "string",
            "description": "Link for footer image",
            "x-example": "https://redhat.com"
          }
        }
      },
      "DOCUMENTATION_ROOT": {
        "type": "string",
        "description": "Root URL for documentation links"
      },
      "HEALTH_CHECKER": {
        "description": "The configured health check.",
        "x-example": [
          "RDSAwareHealthCheck",
          {
            "access_key": "foo",
            "secret_key": "bar"
          }
        ]
      },
      "PROMETHEUS_NAMESPACE": {
        "type": "string",
        "description": "The prefix applied to all exposed Prometheus metrics. Defaults to `quay`",
        "x-example": "myregistry"
      },
      "BLACKLIST_V2_SPEC": {
        "type": "string",
        "description": "The Docker CLI versions to which Quay will respond that V2 is *unsupported*. Defaults to `<1.6.0`",
        "x-reference": "http://pythonhosted.org/semantic_version/reference.html#semantic_version.Spec",
        "x-example": "<1.8.0"
      },
      "USER_RECOVERY_TOKEN_LIFETIME": {
        "type": "string",
        "description": "The length of time a token for recovering a user accounts is valid. Defaults to 30m.",
        "x-example": "10m",
        "pattern": "^[0-9]+(w|m|d|h|s)$"
      },
      "SESSION_COOKIE_SECURE": {
        "type": "boolean",
        "description": "Whether the `secure` property should be set on session cookies. Defaults to False. Recommended to be True for all installations using SSL.",
        "x-example": true,
        "x-reference": "https://en.wikipedia.org/wiki/Secure_cookies"
      },
      "PUBLIC_NAMESPACES": {
        "type": "array",
        "description": "If a namespace is defined in the public namespace list, then it will appear on *all* user's repository list pages, regardless of whether that user is a member of the namespace. Typically, this is used by an enterprise customer in configuring a set of \"well-known\" namespaces.",
        "uniqueItems": true,
        "items": {
          "type": "string"
        }
      },
      "AVATAR_KIND": {
        "type": "string",
        "description": "The types of avatars to display, either generated inline (local) or Gravatar (gravatar)",
        "enum": [
          "local",
          "gravatar"
        ]
      },
      "V2_PAGINATION_SIZE": {
        "type": "number",
        "description": "The number of results returned per page in V2 registry APIs",
        "x-example": 100
      },
      "ENABLE_HEALTH_DEBUG_SECRET": {
        "type": [
          "string",
          "null"
        ],
        "description": "If specified, a secret that can be given to health endpoints to see full debug info whennot authenticated as a superuser",
        "x-example": "somesecrethere"
      },
      "BROWSER_API_CALLS_XHR_ONLY": {
        "type": "boolean",
        "description": "If enabled, only API calls marked as being made by an XHR will be allowed from browsers. Defaults to True.",
        "x-example": false
      },
      "FEATURE_CHANGE_TAG_EXPIRATION": {
        "type": "boolean",
        "description": "Whether users and organizations are allowed to change the tag expiration for tags in their namespace. Defaults to True.",
        "x-example": false
      },
      "DEFAULT_TAG_EXPIRATION": {
        "type": "string",
        "description": "The default, configurable tag expiration time for time machine. Defaults to `2w`.",
        "pattern": "^[0-9]+(w|m|d|h|s)$"
      },
      "TAG_EXPIRATION_OPTIONS": {
        "type": "array",
        "description": "The options that users can select for expiration of tags in their namespace (if enabled)",
        "items": {
          "type": "string",
          "pattern": "^[0-9]+(w|m|d|h|s)$"
        }
      },
      "FEATURE_TEAM_SYNCING": {
        "type": "boolean",
        "description": "Whether to allow for team membership to be synced from a backing group in the authentication engine (LDAP or Keystone)",
        "x-example": true
      },
      "TEAM_RESYNC_STALE_TIME": {
        "type": "string",
        "description": "If team syncing is enabled for a team, how often to check its membership and resync if necessary (Default: 30m)",
        "x-example": "2h",
        "pattern": "^[0-9]+(w|m|d|h|s)$"
      },
      "FEATURE_NONSUPERUSER_TEAM_SYNCING_SETUP": {
        "type": "boolean",
        "description": "If enabled, non-superusers can setup syncing on teams to backing LDAP or Keystone. Defaults To False.",
        "x-example": true
      },
      "FEATURE_SECURITY_SCANNER": {
        "type": "boolean",
        "description": "Whether to turn of/off the security scanner. Defaults to False",
        "x-example": false,
        "x-reference": "https://coreos.com/quay-enterprise/docs/latest/security-scanning.html"
      },
      "FEATURE_SECURITY_NOTIFICATIONS": {
        "type": "boolean",
        "description": "If the security scanner is enabled, whether to turn of/off security notificaitons. Defaults to False",
        "x-example": false
      },
      "SECURITY_SCANNER_V4_ENDPOINT": {
        "type": [
          "string",
          "null"
        ],
        "pattern": "^http(s)?://(.)+$",
        "description": "The endpoint for the V4 security scanner",
        "x-example": "http://192.168.99.101:6060"
      },
      "SECURITY_SCANNER_V4_INDEX_MAX_LAYER_SIZE": {
        "type": [
          "string",
          "null"
        ],
        "description": "Maxmum size for a layer to be indexed",
        "x-example": "8G"
      },
      "SECURITY_SCANNER_V4_MANIFEST_CLEANUP": {
        "type": "boolean",
        "description": "If the security scanner is enabled, whether or not to remove deleted manifests from the security scanner service. Defaults to False",
        "x-example": false
      },
      "SECURITY_SCANNER_INDEXING_INTERVAL": {
        "type": "number",
        "description": "The number of seconds between indexing intervals in the security scanner. Defaults to 30.",
        "x-example": 30
      },
      "SECURITY_SCANNER_V4_PSK": {
        "type": "string",
        "description": "A base64 encoded string used to sign JWT(s) on Clair V4 requests. If 'None' jwt signing will not occur.",
        "x-example": "PSK"
      },
      "REPO_MIRROR_INTERVAL": {
        "type": "number",
        "description": "The number of seconds between checking for repository mirror candidates. Defaults to 30.",
        "x-example": 30
      },
      "FEATURE_GITHUB_BUILD": {
        "type": "boolean",
        "description": "Whether to support GitHub build triggers. Defaults to False",
        "x-example": false
      },
      "FEATURE_BITBUCKET_BUILD": {
        "type": "boolean",
        "description": "Whether to support Bitbucket build triggers. Defaults to False",
        "x-example": false
      },
      "FEATURE_GITLAB_BUILD": {
        "type": "boolean",
        "description": "Whether to support GitLab build triggers. Defaults to False",
        "x-example": false
      },
      "FEATURE_BUILD_SUPPORT": {
        "type": "boolean",
        "description": "Whether to support Dockerfile build. Defaults to True",
        "x-example": true
      },
      "DEFAULT_NAMESPACE_MAXIMUM_BUILD_COUNT": {
        "type": [
          "number",
          "null"
        ],
        "description": "If not None, the default maximum number of builds that can be queued in a namespace.",
        "x-example": 20
      },
      "SUCCESSIVE_TRIGGER_INTERNAL_ERROR_DISABLE_THRESHOLD": {
        "type": [
          "number",
          "null"
        ],
        "description": "If not None, the number of successive internal errors that can occur before a build trigger is automatically disabled. Defaults to 5.",
        "x-example": 10
      },
      "SUCCESSIVE_TRIGGER_FAILURE_DISABLE_THRESHOLD": {
        "type": [
          "number",
          "null"
        ],
        "description": "If not None, the number of successive failures that can occur before a build trigger is automatically disabled. Defaults to 100.",
        "x-example": 50
      },
      "FEATURE_EXTENDED_REPOSITORY_NAMES": {
        "type": "boolean",
        "description": "Whether repository names can have nested paths (/)",
        "x-example": true
      },
      "FEATURE_GITHUB_LOGIN": {
        "type": "boolean",
        "description": "Whether GitHub login is supported. Defaults to False",
        "x-example": false
      },
      "FEATURE_GOOGLE_LOGIN": {
        "type": "boolean",
        "description": "Whether Google login is supported. Defaults to False",
        "x-example": false
      },
      "FEATURE_RECAPTCHA": {
        "type": "boolean",
        "description": "Whether Recaptcha is necessary for user login and recovery. Defaults to False",
        "x-example": false,
        "x-reference": "https://www.google.com/recaptcha/intro/"
      },
      "RECAPTCHA_SITE_KEY": {
        "type": [
          "string",
          "null"
        ],
        "description": "If recaptcha is enabled, the site key for the Recaptcha service"
      },
      "RECAPTCHA_SECRET_KEY": {
        "type": [
          "string",
          "null"
        ],
        "description": "If recaptcha is enabled, the secret key for the Recaptcha service"
      },
      "RECAPTCHA_WHITELISTED_USERS": {
        "type": "array",
        "description": "Quay usernames of those users allowed to create org/user via API bypassing recaptcha security check",
        "uniqueItems": true,
        "items": {
          "type": "string"
        }
      },
      "FEATURE_APP_SPECIFIC_TOKENS": {
        "type": "boolean",
        "description": "If enabled, users can create tokens for use by the Docker CLI. Defaults to True",
        "x-example": false
      },
      "APP_SPECIFIC_TOKEN_EXPIRATION": {
        "type": [
          "string",
          "null"
        ],
        "description": "The expiration for external app tokens. Defaults to None.",
        "pattern": "^[0-9]+(w|m|d|h|s)$"
      },
      "EXPIRED_APP_SPECIFIC_TOKEN_GC": {
        "type": [
          "string",
          "null"
        ],
        "description": "Duration of time expired external app tokens will remain before being garbage collected. Defaults to 1d.",
        "pattern": "^[0-9]+(w|m|d|h|s)$"
      },
      "FEATURE_GARBAGE_COLLECTION": {
        "type": "boolean",
        "description": "Whether garbage collection of repositories is enabled. Defaults to True",
        "x-example": false
      },
      "FEATURE_RATE_LIMITS": {
        "type": "boolean",
        "description": "Whether to enable rate limits on API and registry endpoints. Defaults to False",
        "x-example": true
      },
      "FEATURE_AGGREGATED_LOG_COUNT_RETRIEVAL": {
        "type": "boolean",
        "description": "Whether to allow retrieval of aggregated log counts. Defaults to True",
        "x-example": true
      },
      "FEATURE_LOG_EXPORT": {
        "type": "boolean",
        "description": "Whether to allow exporting of action logs. Defaults to True",
        "x-example": true
      },
      "FEATURE_USER_LAST_ACCESSED": {
        "type": "boolean",
        "description": "Whether to record the last time a user was accessed. Defaults to True",
        "x-example": true
      },
      "FEATURE_PERMANENT_SESSIONS": {
        "type": "boolean",
        "description": "Whether sessions are permanent. Defaults to True",
        "x-example": true
      },
      "FEATURE_SUPER_USERS": {
        "type": "boolean",
        "description": "Whether super users are supported. Defaults to True",
        "x-example": true
      },
      "FEATURE_FIPS": {
        "type": "boolean",
        "description": "If set to true, Quay will run using FIPS compliant hash functions. Defaults to False",
        "x-example": true
      },
      "FEATURE_ANONYMOUS_ACCESS": {
        "type": "boolean",
        "description": " Whether to allow anonymous users to browse and pull public repositories. Defaults to True",
        "x-example": true
      },
      "FEATURE_USER_CREATION": {
        "type": "boolean",
        "description": "Whether users can be created (by non-super users). Defaults to True",
        "x-example": true
      },
      "FEATURE_INVITE_ONLY_USER_CREATION": {
        "type": "boolean",
        "description": "Whether users being created must be invited by another user. Defaults to False",
        "x-example": false
      },
      "FEATURE_REQUIRE_ENCRYPTED_BASIC_AUTH": {
        "type": "boolean",
        "description": "Whether non-encrypted passwords (as opposed to encrypted tokens) can be used for basic auth. Defaults to False",
        "x-example": false
      },
      "FEATURE_DIRECT_LOGIN": {
        "type": "boolean",
        "description": "Whether users can directly login to the UI. Defaults to True",
        "x-example": true
      },
      "FEATURE_ADVERTISE_V2": {
        "type": "boolean",
        "description": "Whether the v2/ endpoint is visible. Defaults to True",
        "x-example": true
      },
      "FEATURE_ACTION_LOG_ROTATION": {
        "type": "boolean",
        "description": "Whether or not to rotate old action logs to storage. Defaults to False",
        "x-example": false
      },
      "FEATURE_LIBRARY_SUPPORT": {
        "type": "boolean",
        "description": "Whether to allow for \"namespace-less\" repositories when pulling and pushing from Docker. Defaults to True",
        "x-example": true
      },
      "FEATURE_REQUIRE_TEAM_INVITE": {
        "type": "boolean",
        "description": "Whether to require invitations when adding a user to a team. Defaults to True",
        "x-example": true
      },
      "FEATURE_USER_METADATA": {
        "type": "boolean",
        "description": "Whether to collect and support user metadata. Defaults to False",
        "x-example": false
      },
      "FEATURE_PUBLIC_CATALOG": {
        "type": "boolean",
        "description": "If set to true, the _catalog endpoint returns public repositories. Otherwise, only private repositories can be returned. Defaults to False",
        "x-example": false
      },
      "FEATURE_READER_BUILD_LOGS": {
        "type": "boolean",
        "description": "If set to true, build logs may be read by those with read access to the repo, rather than only write access or admin access. Defaults to False",
        "x-example": false
      },
      "FEATURE_PARTIAL_USER_AUTOCOMPLETE": {
        "type": "boolean",
        "description": "If set to true, autocompletion will apply to partial usernames. Defaults to True",
        "x-example": true
      },
      "FEATURE_USER_LOG_ACCESS": {
        "type": "boolean",
        "description": "If set to true, users will have access to audit logs for their namespace. Defaults to False",
        "x-example": true
      },
      "FEATURE_USER_RENAME": {
        "type": "boolean",
        "description": "If set to true, users can rename their own namespace. Defaults to False",
        "x-example": true
      },
      "FEATURE_USERNAME_CONFIRMATION": {
        "type": "boolean",
        "description": "If set to true, users can confirm their generated usernames. Defaults to True",
        "x-example": false
      },
      "FEATURE_RESTRICTED_V1_PUSH": {
        "type": "boolean",
        "description": "If set to true, only namespaces listed in V1_PUSH_WHITELIST support V1 push. Defaults to True",
        "x-example": false
      },
      "FEATURE_REPO_MIRROR": {
        "type": "boolean",
        "description": "Whether to enable support for repository mirroring. Defaults to False",
        "x-example": false
      },
      "REPO_MIRROR_TLS_VERIFY": {
        "type": "boolean",
        "description": "Require HTTPS and verify certificates of Quay registry during mirror. Defaults to True",
        "x-example": true
      },
      "REPO_MIRROR_SERVER_HOSTNAME": {
        "type": [
          "string",
          "null"
        ],
        "description": "Replaces the SERVER_HOSTNAME as the destination for mirroring. Defaults to unset",
        "x-example": "openshift-quay-service"
      },
      "REPO_MIRROR_ROLLBACK": {
        "type": [
          "boolean",
          "null"
        ],
        "description": "Enables rolling repository back to previous state in the event the mirror fails. Defaults to false",
        "x-example": "true"
      },
      "V1_PUSH_WHITELIST": {
        "type": "array",
        "description": "The array of namespace names that support V1 push if FEATURE_RESTRICTED_V1_PUSH is set to true.",
        "x-example": [
          "some",
          "namespaces"
        ]
      },
      "LOGS_MODEL": {
        "type": "string",
        "description": "Logs model for action logs",
        "enum": [
          "database",
          "transition_reads_both_writes_es",
          "elasticsearch",
          "splunk"
        ],
        "x-example": "database"
      },
      "LOGS_MODEL_CONFIG": {
        "type": "object",
        "description": "Logs model config for action logs",
        "x-reference": "https://www.elastic.co/guide/en/elasticsearch/guide/master/_index_settings.html",
        "properties": {
          "producer": {
            "type": "string",
            "description": "Logs producer",
            "enum": [
              "kafka",
              "elasticsearch",
              "kinesis_stream",
              "splunk"
            ],
            "x-example": "kafka"
          },
          "elasticsearch_config": {
            "type": "object",
            "description": "Elasticsearch cluster configuration",
            "properties": {
              "host": {
                "type": "string",
                "description": "Elasticsearch cluster endpoint",
                "x-example": "host.elasticsearch.example"
              },
              "port": {
                "type": "number",
                "description": "Elasticsearch cluster endpoint port",
                "x-example": 1234
              },
              "access_key": {
                "type": "string",
                "description": "Elasticsearch user (or IAM key for AWS ES)",
                "x-example": "some_string"
              },
              "secret_key": {
                "type": "string",
                "description": "Elasticsearch password (or IAM secret for AWS ES)",
                "x-example": "some_secret_string"
              },
              "aws_region": {
                "type": "string",
                "description": "Amazon web service region",
                "x-example": "us-east-1"
              },
              "use_ssl": {
                "type": "boolean",
                "description": "Use ssl for Elasticsearch. Defaults to True",
                "x-example": true
              },
              "index_prefix": {
                "type": "string",
                "description": "Elasticsearch's index prefix",
                "x-example": "logentry_"
              },
              "index_settings": {
                "type": "object",
                "description": "Elasticsearch's index settings"
              }
            }
          },
          "kafka_config": {
            "type": "object",
            "description": "Kafka cluster configuration",
            "properties": {
              "bootstrap_servers": {
                "type": "array",
                "description": "List of Kafka brokers to bootstrap the client from",
                "uniqueItems": true,
                "items": {
                  "type": "string"
                }
              },
              "topic": {
                "type": "string",
                "description": "Kafka topic to publish log entries to",
                "x-example": "logentry"
              },
              "max_block_seconds": {
                "type": "number",
                "description": "Max number of seconds to block during a `send()`, either because the buffer is full or metadata unavailable",
                "x-example": 10
              }
            }
          },
          "kinesis_stream_config": {
            "type": "object",
            "description": "AWS Kinesis Stream configuration",
            "properties": {
              "stream_name": {
                "type": "string",
                "description": "Kinesis stream to send action logs to",
                "x-example": "logentry-kinesis-stream"
              },
              "aws_region": {
                "type": "string",
                "description": "AWS region",
                "x-example": "us-east-1"
              },
              "aws_access_key": {
                "type": "string",
                "description": "AWS access key",
                "x-example": "some_access_key"
              },
              "aws_secret_key": {
                "type": "string",
                "description": "AWS secret key",
                "x-example": "some_secret_key"
              },
              "connect_timeout": {
                "type": "number",
                "description": "Number of seconds before timeout when attempting to make a connection",
                "x-example": 5
              },
              "read_timeout": {
                "type": "number",
                "description": "Number of seconds before timeout when reading from a connection",
                "x-example": 5
              },
              "retries": {
                "type": "number",
                "description": "Max number of attempts made on a single request",
                "x-example": 5
              },
              "max_pool_connections": {
                "type": "number",
                "description": "The maximum number of connections to keep in a connection pool",
                "x-example": 10
              }
            }
          },
          "splunk_config": {
            "type": "object",
            "description": "Logs model config for splunk action logs/ splunk cluster configuration",
            "x-reference": "https://dev.splunk.com/enterprise/docs/devtools/python/sdk-python/howtousesplunkpython/howtogetdatapython#To-add-data-directly-to-an-index",
            "properties": {
              "host": {
                "type": "string",
                "description": "Splunk cluster endpoint",
                "x-example": "host.splunk.example"
              },
              "port": {
                "type": "number",
                "description": "Splunk management cluster endpoint port",
                "x-example": 1234
              },
              "bearer_token": {
                "type": "string",
                "description": "Bearer_Token for splunk.See: https://dev.splunk.com/enterprise/docs/devtools/python/sdk-python/howtousesplunkpython/howtoconnectpython/#Log-in-using-a-bearer-token",
                "x-example": "us-east-1"
              },
              "url_scheme": {
                "type": "string",
                "description": "The url scheme for accessing the splunk service. If Splunk is behind SSL*at all*, this *must* be `https`",
                "enum": [
                  "http",
                  "https"
                ],
                "x-example": "https"
              },
              "verify_ssl": {
                "type": "boolean",
                "description": "Enable (True) or disable (False) SSL verification for https connections.Defaults to True",
                "x-example": true
              },
              "index_prefix": {
                "type": "string",
                "description": "Splunk's index prefix",
                "x-example": "splunk_logentry_"
              },
              "ssl_ca_path": {
                "type": "string",
                "description": "*Relative container path* to a single .pem file containing a CA certificate for SSL verification",
                "x-example": "conf/stack/ssl-ca-cert.pem"
              }
            }
          }
        }
      },
      "FEATURE_BLACKLISTED_EMAILS": {
        "type": "boolean",
        "description": "If set to true, no new User accounts may be created if their email domain is blacklisted.",
        "x-example": false
      },
      "BLACKLISTED_EMAIL_DOMAINS": {
        "type": "array",
        "description": "The array of email-address domains that is used if FEATURE_BLACKLISTED_EMAILS is set to true.",
        "x-example": [
          "example.com",
          "example.org"
        ]
      },
      "FRESH_LOGIN_TIMEOUT": {
        "type": "string",
        "description": "The time after which a fresh login requires users to reenter their password",
        "x-example": "5m"
      },
      "WEBHOOK_HOSTNAME_BLACKLIST": {
        "type": "array",
        "description": "The set of hostnames to disallow from webhooks when validating, beyond localhost",
        "x-example": [
          "somexternaldomain.com"
        ]
      },
      "CREATE_PRIVATE_REPO_ON_PUSH": {
        "type": "boolean",
        "description": "Whether new repositories created by push are set to private visibility. Defaults to True.",
        "x-example": true
      },
      "CREATE_NAMESPACE_ON_PUSH": {
        "type": "boolean",
        "description": "Whether new push to a non-existent organization creates it. Defaults to False.",
        "x-example": false
      },
      "FEATURE_USER_INITIALIZE": {
        "type": "boolean",
        "description": "If set to true, the first User account may be created via API /api/v1/user/initialize",
        "x-example": false
      },
      "ALLOWED_OCI_ARTIFACT_TYPES": {
        "type": "object",
        "description": "The set of allowed OCI artifact mimetypes and the assiciated layer types",
        "x-example": {
          "application/vnd.cncf.helm.config.v1+json": [
            "application/tar+gzip"
          ],
          "application/vnd.sylabs.sif.config.v1+json": [
            "application/vnd.sylabs.sif.layer.v1.sif"
          ]
        }
      },
      "CLEAN_BLOB_UPLOAD_FOLDER": {
        "type": "boolean",
        "description": "Automatically clean stale blobs leftover in the uploads storage folder from cancelled uploads",
        "x-example": false
      },
      "FEATURE_QUOTA_MANAGEMENT": {
        "type": "boolean",
        "description": "Enables configuration, caching, and validation for quota management feature",
        "x-example": false
      },
      "DEFAULT_SYSTEM_REJECT_QUOTA_BYTES": {
        "type": "int",
        "description": "Enables system default quota reject byte allowance for all organizations",
        "x-example": false
      },
      "QUOTA_TOTAL_DELAY_SECONDS": {
        "type": "int",
        "description": "The time to delay the Quota backfill operation. Must be set longer than the time required to complete the deployment.",
        "x-example": 30
      },
      "QUOTA_BACKFILL": {
        "type": "boolean",
        "description": "Enables the quota backfill worker to calculate the size of pre-existing blobs",
        "x-example": true
      },
      "QUOTA_BACKFILL_POLL_PERIOD": {
        "type": "int",
        "description": "The amount of time between runs of the quota backfill worker in seconds",
        "x-example": 15
      },
      "QUOTA_BACKFILL_BATCH_SIZE": {
        "type": "int",
        "description": "The amount of namespaces that will be calculated for quota backfill on wakeup of the backfill worker.",
        "x-example": 100
      },
      "QUOTA_INVALIDATE_TOTALS": {
        "type": "boolean",
        "description": "Invalidates totals when a write happens to a namespace and repository when FEATURE_QUOTA_MANAGEMENT is not enabled",
        "x-example": true
      },
      "QUOTA_REGISTRY_SIZE_POLL_PERIOD": {
        "type": "int",
        "description": "The amount of time between runs of the quota registry size worker in seconds",
        "x-example": 30
      },
      "FEATURE_EXPORT_COMPLIANCE": {
        "type": "boolean",
        "description": "Use Red Hat Export Compliance Service during Red Hat SSO (only used in Quay.io)",
        "x-example": false
      },
      "UI_V2_FEEDBACK_FORM": {
        "type": "string",
        "description": "User feedback form for UI-V2",
        "x-example": "http://url-for-user-feedback-form.com"
      },
      "FEATURE_UI_V2": {
        "type": "boolean",
        "description": "Enables user to try the beta UI Environment",
        "x-example": false
      },
      "FEATURE_UI_V2_REPO_SETTINGS": {
        "type": "boolean",
        "description": "Enables repository settings in the beta UI Environment",
        "x-example": false
      },
      "EXPORT_COMPLIANCE_ENDPOINT": {
        "type": "string",
        "description": "The Red Hat Export Compliance Service Endpoint (only used in Quay.io)",
        "x-example": "export-compliance.com"
      },
      "CORS_ORIGIN": {
        "type": "array",
        "description": "Cross-Origin domain to allow requests from",
        "x-example": [
          "localhost:9000",
          "localhost:8080"
        ]
      },
      "FEATURE_LISTEN_IP_VERSION": {
        "type": "string",
        "description": "Enables IPv4, IPv6 or dual-stack networking. Defaults to `IPv4`.",
        "x-example": "IPv4"
      },
      "GLOBAL_READONLY_SUPER_USERS": {
        "type": "array",
        "description": "Quay usernames of those super users to be granted global readonly privileges",
        "uniqueItems": true,
        "items": {
          "type": "string"
        }
      },
      "FEATURE_SUPERUSERS_FULL_ACCESS": {
        "type": "boolean",
        "description": "Grant superusers full access to repositories, registry-wide",
        "x-example": false
      },
      "FEATURE_SUPERUSERS_ORG_CREATION_ONLY": {
        "type": "boolean",
        "description": "Whether to only allow superusers to create organizations",
        "x-example": false
      },
      "FEATURE_RESTRICTED_USERS": {
        "type": "boolean",
        "description": "Grant non-whitelisted users restricted permissions",
        "x-example": false
      },
      "RESTRICTED_USERS_WHITELIST": {
        "type": "array",
        "description": "Whitelisted users to exclude when FEATURE_RESTRICTED_USERS is enabled",
        "x-example": [
          "devtable"
        ]
      },
      "FEATURE_SECURITY_SCANNER_NOTIFY_ON_NEW_INDEX": {
        "type": "boolean",
        "description": "Whether to allow sending notifications about vulnerabilities for new pushes",
        "x-example": true
      },
      "RESET_CHILD_MANIFEST_EXPIRATION": {
        "type": "boolean",
        "description": "When a manifest list is pushed, reset the expiry of the child manifest tags to become immediately eligible for GC on parent tag deletion",
        "x-example": true
      },
      "PERMANENTLY_DELETE_TAGS": {
        "type": "boolean",
        "description": "Enables functionality related to the removal of tags from the time machine window",
        "x-example": true
      },
      "FEATURE_ENTITLEMENT_RECONCILIATION": {
        "type": "boolean",
        "description": "Enable reconciler for internal RH marketplace",
        "x-example": false
      },
      "ENTITLEMENT_RECONCILIATION_USER_ENDPOINT": {
        "type": "string",
        "description": "Endpoint for internal RH users API",
        "x-example": "https://internal-rh-user-endpoint"
      },
      "ENTITLEMENT_RECONCILIATION_MARKETPLACE_ENDPOINT": {
        "type": "string",
        "description": "Endpoint for internal RH marketplace API",
        "x-example": "https://internal-rh-marketplace-endpoint"
      },
      "TERMS_OF_SERVICE_URL": {
        "type": "string",
        "description": "Enable customizing of terms of service for on-prem installations",
        "x-example": "https://quay.io/tos"
      },
      "ROBOTS_DISALLOW": {
        "type": "boolean",
        "description": "If robot accounts are prevented from any interaction as well as from being created. Defaults to False"
      },
      "ROBOTS_WHITELIST": {
        "type": "array",
        "description": "List of robot accounts allowed for example, mirroring. Defaults to empty"
      },
      "FEATURE_AUTO_PRUNE": {
        "type": "boolean",
        "description": "Enable functionality related to the auto-pruning of tags",
        "x-example": false
      },
      "FEATURE_UI_DELAY_AFTER_WRITE": {
        "type": "boolean",
        "description": "Adds a delay in the UI after each create operation. Useful if quay is reading from a different DB and there is replication delay between the write and read DBs. Defaults to False",
        "x-example": false
      },
      "UI_DELAY_AFTER_WRITE_SECONDS": {
        "type": "int",
        "description": "Number of seconds to wait after a write operation in the UI",
        "x-example": 3
      }
    }
  }
}

@ibazulic
Copy link
Member

Oh, I see, so much more data will be available to the administrator with this endpoint. Yeah, that does require it to be under a super suer API. Sounds good to me!

ibazulic
ibazulic previously approved these changes Sep 24, 2024
@michaelalang
Copy link
Contributor Author

@ibazulic thanks for checking in <3

# we shouldn't populate keys with methods/class/functions
# but to ensure we do not raise an Exception
app.logger.error(f"Cannot parse json, error {jsonerr}")
return dict(config=str(cfg), warning=str(warn), env=dict(os.environ))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why convert cfg and warn to string? Should the above try except catch any errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line with the previous comment (and mybe that helps understanding the logic).

we process dictionaries with possible sub dictionaries / arrays and any item in those iterable might contain a function (typically those are the flask methods) ...

so that give as example

for key in iterable:
   iterable.get(key) # can be another iterable like dict/array 

and any of those sub iterable keys if being a function is not obfuscate able so we ignore it
on the other exception handling of the method process_config the conversion to strings of the content of cfg and warn is done if there's any new method in those which is why we convert them to strings as otherwise the API call will break between version compared to returning in stringified json content.

Does this make more sense for you now ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you give an example for when this will be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure ... so with

def process_config():
            for k, v in app.config.items():
                if not type(v) in (list, dict, tuple, int, str, bool):
                    continue

if any of the items would return a class for example ... than if the key isn't in the obfuscate list of verbs to be protected, it would return the content in the dict like

{ "somekey": <class ....>, ...}

if that type of class isn't serializable for json it would raise an exception instead it will return the stringified represenation of the complete dict.

>>> import json
>>> class Something():  pass
>>> json.dumps(dict(key1=1, key2="str", key3=Something()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.9/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib64/python3.9/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib64/python3.9/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib64/python3.9/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Something is not JSON serializable

compared to

>>> json.dumps(dict(key1=1, key2="str", key3=str(Something())))
'{"key1": 1, "key2": "str", "key3": "<__main__.Something object at 0x7f054eac0640>"}'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant an example of a key where class is returned in value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sunandadadi for example ..

oc -n quay exec -ti deploy/quay -- python
>>> import app
Failed to validate security scanner V4 configuration
>>> app.app.config["HTTPCLIENT"]
<requests.sessions.Session object at 0x7f30b261e070>
>>> app.app.config["LOGS_MODEL_CONFIG"]
{'should_skip_logging': <function configure.<locals>.should_skip_logging at 0x7f30b0fa51f0>}
>>>

@Sunandadadi
Copy link
Contributor

The PR description mentions schema key is expected. I don't see it in the curl response or in the code.
Added a few other minor comments otherwise looks good. Can you also add a test to the endpoint?

@michaelalang
Copy link
Contributor Author

The PR description mentions schema key is expected. I don't see it in the curl response or in the code. Added a few other minor comments otherwise looks good. Can you also add a test to the endpoint?

ack ... good catch ... will add the unittest accordingly

@michaelalang
Copy link
Contributor Author

@Sunandadadi I added the unittests as well anything else we need to get this rolling ?

@Sunandadadi
Copy link
Contributor

@michaelalang thank you for adding tests. Do you want to address this comment in this PR?

…ended to return data

bug: fixing NaN value error for quota displayed on member org page (PROJQUAY-6465) (quay#3224)

bug: fixing NaN value error for quota displayed on member org page (PROJQUAY-6465)

fixed black formatting

fixed flake and black formatting

fixed isort formatting

test need to be updated for superuser endpoints. There is no explicit superuser token test so globalreadonlysuperuser shall succeed too

fixed double json encoding

changed naming to comply with other SuperUserClasses, added SuperUserPermission check as scope only isnt sufficient

fixed another black error

fixed response for devtable check

fixed response for devtable as that is a superuser

fixed black format :/

added allow_if_global_readonly_superuser to config endpoint

repush for checks

fixed app.logger to module specific logger ; added missed SCHEMA return

added unittest for checking superuser config dump API call (no clue if the unittests build up a full setup since we mock all kind of stuff in the other calls)

removed env PWD check as it seems to be unset in the github runners

added missing unittest step

added FeatureFlag for config dump

formatting
@michaelalang
Copy link
Contributor Author

@michaelalang thank you for adding tests. Do you want to address this comment in this PR?

@Sunandadadi added the feature flag accordingly. Had to rebase as config.py was in conflict.

@michaelalang michaelalang force-pushed the PROJQUAY-4559 branch 4 times, most recently from 506b6c0 to 141a149 Compare March 25, 2025 12:35
… to fail as the default config is to deny the request
@michaelalang michaelalang force-pushed the PROJQUAY-4559 branch 2 times, most recently from 944264f to a998f3e Compare March 25, 2025 15:24
@michaelalang michaelalang force-pushed the PROJQUAY-4559 branch 2 times, most recently from 7ff5244 to e64c6d7 Compare March 25, 2025 17:44
@michaelalang
Copy link
Contributor Author

@Sunandadadi should be working now as expected with:

  • Feature Flag
  • unitttests
  • and the missing schema output

please note that the schema update PR is necessary to have all featues included ...

Copy link
Contributor

@Sunandadadi Sunandadadi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay on this. Added one minor comment otherwise everything looks good. Thank you for adding tests.


@resource("/v1/superuser/config")
@show_if(features.SUPER_USERS)
class SuperUserDumpConfig(ApiResource):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add another decorator here for features.SUPERUSER_CONFIGDUMP and remove here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sunandadadi no that doesn't work as other checks on in the unittest list are expecting to access that endpoint I tried and adding it afterwards was the best I was able to come up with ..

Copy link
Contributor

@Sunandadadi Sunandadadi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@Sunandadadi Sunandadadi merged commit 8332d99 into quay:master Apr 28, 2025
17 of 18 checks passed
Sunandadadi pushed a commit that referenced this pull request Apr 30, 2025
…nce reasons (PROJQUAY-4559) (#3253)

* initial checkin for the superuser/config endpoint to show how its intended to return data

bug: fixing NaN value error for quota displayed on member org page (PROJQUAY-6465) (#3224)

bug: fixing NaN value error for quota displayed on member org page (PROJQUAY-6465)

fixed black formatting

fixed flake and black formatting

fixed isort formatting

test need to be updated for superuser endpoints. There is no explicit superuser token test so globalreadonlysuperuser shall succeed too

fixed double json encoding

changed naming to comply with other SuperUserClasses, added SuperUserPermission check as scope only isnt sufficient

fixed another black error

fixed response for devtable check

fixed response for devtable as that is a superuser

fixed black format :/

added allow_if_global_readonly_superuser to config endpoint

repush for checks

fixed app.logger to module specific logger ; added missed SCHEMA return

added unittest for checking superuser config dump API call (no clue if the unittests build up a full setup since we mock all kind of stuff in the other calls)

removed env PWD check as it seems to be unset in the github runners

added missing unittest step

added FeatureFlag for config dump

formatting

* removed wrong commit in the branch

* changed from route decorator to in method check and changed unittests to fail as the default config is to deny the request

* added one test for security_tests

* rebumped the security tests
bcaton85 added a commit to bcaton85/quay that referenced this pull request Apr 30, 2025
* chore: fix for wide open ssh for vsi for Z (quay#3591)

fix for wide open ssh for vsi

* ui: Expand support for customized footer links (PROJQUAY-5648) (quay#3556)

* ui: Expand support for customized footer links (PROJQUAY-5648)
Previous iteration only allowed changes to the terms of service. With this push, all footer links should be customizable through the `FOOTER_LINKS` object. Example:

~~~
FOOTER_LINKS:
  TERMS_OF_SERVICE_URL: "some_url"
  PRIVACY_POLICY_URL: "some_url"
  SECURITY_URL: "some_url"
  ABOUT_URL: "some_url"
~~~

Missing entries will not be printed out in the UI.

* Fixes to parsing of config object

* Add type annotation

* chore: Filtering repeatPassword in debuglogs (PROJQUAY-8559) (quay#3659)

* Filtering repeatPassword in debuglogs

* updated test_log_util.py for format issues

* chore: Added pull-push SLI panel based on ALB datapoints (PROJQUAY-8506) (quay#3647)

* Added pull-push SLI panel based on ALB datapoints

---------

Co-authored-by: shudeshp <shudeshp@redhat.com>

* chore: updated target group names in stage dashboard (PROJQUAY-8506) (quay#3672)

updated target group names in stage dashboard

Co-authored-by: shudeshp <shudeshp@redhat.com>

* dashboard: add usage dashboard for grafana (PROJQUAY-8509) (quay#3658)

add usage dashboard for grafana

* chore: Added SLO dashboards based on ALB metrics (PROJQUAY-8506)  (quay#3684)

* updating panels to display correct load balancers and target groups

* added ELB errors in the SLO calculations

* Added corrected ELB errors

---------

Co-authored-by: shudeshp <shudeshp@redhat.com>

* chore: Updated push pull SLO panels (PROJQUAY-8506) (quay#3685)

* corrected alb and target names

* updated push pull SLO panels

---------

Co-authored-by: shudeshp <shudeshp@redhat.com>

* chore: corrected Invalid Json Formatting (PROJQUAY-8506) (quay#3686)

reformatted json

Co-authored-by: shudeshp <shudeshp@redhat.com>

* minor: added missing sign for var (quay#3687)

* added missing sign for var

---------

Co-authored-by: shudeshp <shudeshp@redhat.com>

* minor: removed unused properties from Pull SLO (PROJQUAY-8506) (quay#3688)

removed unused properties from Pull SLO

Co-authored-by: shudeshp <shudeshp@redhat.com>

* Pull slo (quay#3689)

* corrected id on the pull slo panel

---------

Co-authored-by: shudeshp <shudeshp@redhat.com>

* deploying with corrected source (quay#3690)

Co-authored-by: shudeshp <shudeshp@redhat.com>

* Update CI-nightly.yaml

Updated changes in IBM Cloud profile names for Z machines.

* storage(cloudfront): fixed presign uri for multi-region (PROJQUAY-8532) (quay#3666)

Fixed super initialize to include region_name in CloudFrontedS3Storage.

* Update CI-nightly.yaml

Fix wide open SSH port and modify ci-nightly file for a more organized format.

* chore: corrected error budget left calculations (PROJQUAY-8506) (quay#3695)

Corrected error budget left calculations

Co-authored-by: shudeshp <shudeshp@redhat.com>

* chore: Change in the CIDR for Z (quay#3693)

* storage: fix format error (PROJQUAY-8610) (quay#3697)

* ui: implement change to render modelcard stored in layers (PROJQUAY-8642) (quay#3692)

* ui: implement change to render modelcard stored in layers (PROJQUAY-8412)

When a manifest has certain annotations or artifactTypes, render the
applicable modelcard markdown in a new tags detail tab.

* removing untar when fetching model card

* removing extra api calls

* Add modelcar check tests

---------

Co-authored-by: bcaton <bcaton@redhat.com>

* healthcheck: add option to check preferred storage during instance check (PROJQUAY-5074) (quay#2854)

* api: looking up layer by artifact type (PROJQUAY-8644) (quay#3701)

Fixes a bug where the annotation is required at the manifest level even if artifactType is present. The modelcard should only be indicated by the artifact type and layer annotation for oci artifacts.

* chore: upgrade jinja to 3.1.6 (PROJQUAY-8657) (quay#3706)

* [Feature] storage: Modify the STS S3 implementation of the storage backend to use Web Identity Tokens when available (PROJQUAY-8576) (quay#3670)

When deploying Quay in a Secure AWS environment, we can't use IAM Access Keys or Secrets since these credentials are often blocked for multiple reasons (credentials are long-lived, can be shared / stolen, etc.). So the preferred deployment method is to use an alternative method, like the Web Identity Token files that are automatically created in a Kubernetes cluster that has a federation link with IAM using the OIDC provider federation.

The current code of Quay force the use of an IAM account that is then used to assume another role that has S3 access to store the image files. The current pull request removes the need to use that IAM account and allows to directly assume the correct role using Web Identity Tokens while retaining compatibility with the old method of using IAM credentials.

The code relies on the automatic detection of the correct configurations using environment variables where possible. The code has been tested on an OpenShift cluster deployed using manual mode with AWS STS.

* bug: Fix security url template variable (PROJQUAY-8650) (quay#3707)

chore: Fix security url template variable (PROJQUAY-8650)
Fixes the wrong name of the variable for the security link in the base template. All links should show properly now.

* bug: Adding allow hidden flag while looking up for manifests (PROJQUAY-8536) (quay#3722)

When an image is pulled by digest, a temp tag is created to prevent the manifest from being garbage collected. This is true when a manifest list is pulled by tag as well. However, if this temporary tag expires (default is 1 day for proxied organizations) and the same manifest is pulled again by digest, the system attempts to create the manifest again, leading to an integrity error because the manifest already exists in the database.

---------

Co-authored-by: shudeshp <shudeshp@redhat.com>

* billing: stop modifying subscription list that is being iterated over (PROJQUAY-8712) (quay#3725)

Fixes bug where removing a MW02702 sub after all it's quantities have been bound causes the next item in the subscription list to be skipped over, resulting in a malformed api response for the marketplace endpoint.

* nit: change ModelCard to Model Card (PROJQUAY-8716) (quay#3727)

* chore: add test case for PROJQUAY-8712 (PROJQUAY-8712) (quay#3728)

add test case for PROJQUAY-8712

* deps: bump gunicorn (PROJQUAY-8726) (quay#3731)

remove package-lock.json from the pr

* fix(ui): corrected pull column alignment in tag view (PROJQUAY-8623) (quay#3730)

PROJQUAY-8623: In Tag view, Pull column alignment doesn't consistent with others

* healthchecks: Use httpGet for liveness and readiness probe checks (PROJQUAY-8747) (quay#3743)

* Use httpget for liveness and readiness probe checks

* update liveness period seconds

* modelcard: Setting model card feature to false by default (quay#3744)

modelcard: Setting model card feature to false on quay.io

* operations: added ELB calculations to ALB based SLO timeseries (PROJQUAY-8508) (quay#3747)

added ELB calculations to ALB based SLO timeseries

Co-authored-by: shudeshp <shudeshp@redhat.com>

* operations: corrected metric expression to span over all targets (PROJQUAY-8508) (quay#3749)

corrected metric expression to span over all targets

Co-authored-by: shudeshp <shudeshp@redhat.com>

* operations: removing unused datasources (PROJQUAY-8508) (quay#3750)

removing unused datasources

Co-authored-by: shudeshp <shudeshp@redhat.com>

* healthcheck: Make gunicorn health check timeout configurable (PROJQUAY-8757) (quay#3746)

healthcheck: Make gunicorn health check timeout configurable

* deploy: Adding graceful shutdown on pods (PROJQUAY-8760) (quay#3753)

deploy: Adding graceful shutdown on pods

* storage: Enable multipart upload for Google Cloud Storage (PROJQUAY-6862) (quay#3748)

* storage: Enable multipart upload for Google Cloud Storage (PROJQUAY-6862)
This PR removes the `_stream_write_internal` function override that caused excessive memory consumption and defaults to the old one which chunks uploads. Server assembly is still not suppored by GCS, so we have to assemble everything locally. However, GCS does support the copy function, so a reupload is not needed.

~~~
REPOSITORY                                        TAG         IMAGE ID      CREATED      SIZE
registry.fedoraproject.org/fedora                 latest      ecd9f7ee77f4  2 days ago   165 MB
quay.skynet/ibazulic/big-mirror-test              size138gb   8e6ba9ff13c0  3 days ago   148 GB
quay.skynet/quay-mirror/big-mirror-test           size138gb   8e6ba9ff13c0  3 days ago   148 GB
quay.skynet/ibazulic/mfs-image-test               latest      ab14f2230dd9  7 days ago   5.96 GB
quay.skynet/ibazulic/azure-storage-big-file-test  latest      ede194b926e0  7 days ago   16.1 GB
quay.skynet/ibazulic/minio/minio                  latest      76ed5b96833a  6 weeks ago  532 B

Getting image source signatures
Copying blob 9d9c3d76c421 done   |
Copying blob fce7cf3b093c skipped: already exists
Copying config 8e6ba9ff13 done   |
Writing manifest to image destination
~~~

For uploading extremely big layers, 5 MiB as the default chunk size is not enough. The PR also enables support for user-defined chunk sizes via `minimum_chunk_size_mb` and `maximum_chunk_size_mb` which default to 5 Mib and 100 MiB respectively.

* Remove maximum_chunk_size_mb as it's not needed

* ui: render modelcard markdown tables (PROJQUAY-8680) (quay#3708)

* ui: render modelcard markdown tables (PROJQUAY-8680)

* ui: oembed to render embeded video in markdown (PROJQUAY-8679)

* ui: render tables and embeded links in markdown (PROJQUAY-8673)

* Github linked videos and Patternfly code block

* Limit img source to github and huggingface

* marketplace: free tier integration for reconciler (PROJQUAY-5698) (quay#3589)

free sku integration for reconciliation worker

* db: moving get user repo permissions query to read replica (PROJQUAY-8792) (quay#3772)

* Revert "deploy: Adding graceful shutdown on pods (PROJQUAY-8760)" (quay#3775)

Revert "deploy: Adding graceful shutdown on pods (PROJQUAY-8760) (quay#3753)"

This reverts commit c25be58.

* Revert "healthcheck: Make gunicorn health check timeout configurable (PROJQUAY-8757)" (quay#3774)

Revert "healthcheck: Make gunicorn health check timeout configurable (PROJQUA…"

This reverts commit be08d48.

* Revert "healthchecks: Use httpGet for liveness and readiness probe checks (PROJQUAY-8747)" (quay#3776)

Revert "healthchecks: Use httpGet for liveness and readiness probe checks (PR…"

This reverts commit ea1d18d.

* scripts: clean up old container in frontend build script (PROJQUAY-0000) (quay#3777)

* clean up old container in frontend build script

* ensure a non-zero exit code for docker rm

* db: moving get user from username query to read replica (PROJQUAY-8792) (quay#3773)

* db: moving robot search query to read replica (PROJQUAY-8792) (quay#3781)

* reconciler: fix typo in exception type (PROJQUAY-0000) (quay#3779)

* fix typo in exception type

* update test cases

* chore: move github runners to ubuntu-22.04 (quay#3783)

* chore: move github runners to ubuntu-22.04

* use docker image with openssl 1.1 preinstalled

* using non-interactive mode for github actions

* remove starting docker

* remove starting docker service

* install openssl 1.1 on ubuntu-22.04

* minor fixes

* compiling from source

* check openssl version

* check openssl version before running tox

* use exports when running tox

* fix typo

* overwrite OPENSSL_VERSION var

* minor fixes

* use python3.9 before installing openssl-1.1

* download python and configure openssl1.1

* adding sudo to configure

* use sudo for make

* minor fixes

* using python venv to run tox

* Apply changes to all tests

* db: moving get sorted matching repos and find repos to garbage collect to read replica (PROJQUAY-8792) (quay#3782)

* bug: make changes to taghistory page to accept manually entered date (PROJQUAY-8633) (quay#3752)

projquay-8633 accepting dates in DD MMM YYYY format + calendar button is visible + fixing alignment across entire toolbox + improved logic for consistency across different browser language settings

* db: revert get_namespace_user from read replica (PROJQUAY-8792) (quay#3796)

* db: moving robot search and find repo to garbage collect queries to read replica (PROJQUAY-8792) (quay#3795)

* db: moving robot search and find repository to garbage collect queries to read replica (PROJQUAY-8792)

* removing lookup_robot from read_replica

* reconciler: fix exception when user api is called with empty email (PROJQUAY-5698) (quay#3798)

* fix exception when user api is called with empty email

* reconciler: Remove database calls for storing/changing web customer ids (PROJQUAY-0000) (quay#3799)

Remove database calls for storing/changing web customer ids
during reconciler run

* gc: garbage collect manifests not targetted by any tags when deleting repository (PROJQUAY-8136) (quay#3797)

* gc: garbage collect manifests not targetted by any tags when deleting repository (PROJQUAY-8136)

* test untagged manifest removal

* chore: update moment version in cdn (PROJQUAY-8781) (quay#3766)

* proxy: moving manifest check to after upstream manifest fetch (PROJQUAY-8536) (quay#3814)

moving manifest check to after upstream manifest fetch

* dockerfile: dockerfile changes for konflux (PROJQUAY-8804) (quay#3817)

dockerfile changes for konflux

* db: optimize _get_user_repo_permissions to send to read replica (PROJQUAY-8839) (quay#3818)

* db: optimize _get_user_repo_permissions to send to read replica (PROJQUAY-8839)

it uses a union query which doesn't invoke the replica selection
logic. Make this into 2 seperate queries

* fix unit tests

* logging: fix unreferenced variable from logging (PROJQUAY-8136) (quay#3819)

* endpoints(v1/superuser/config): adding a full config dump for compliance reasons (PROJQUAY-4559) (quay#3253)

* initial checkin for the superuser/config endpoint to show how its intended to return data

bug: fixing NaN value error for quota displayed on member org page (PROJQUAY-6465) (quay#3224)

bug: fixing NaN value error for quota displayed on member org page (PROJQUAY-6465)

fixed black formatting

fixed flake and black formatting

fixed isort formatting

test need to be updated for superuser endpoints. There is no explicit superuser token test so globalreadonlysuperuser shall succeed too

fixed double json encoding

changed naming to comply with other SuperUserClasses, added SuperUserPermission check as scope only isnt sufficient

fixed another black error

fixed response for devtable check

fixed response for devtable as that is a superuser

fixed black format :/

added allow_if_global_readonly_superuser to config endpoint

repush for checks

fixed app.logger to module specific logger ; added missed SCHEMA return

added unittest for checking superuser config dump API call (no clue if the unittests build up a full setup since we mock all kind of stuff in the other calls)

removed env PWD check as it seems to be unset in the github runners

added missing unittest step

added FeatureFlag for config dump

formatting

* removed wrong commit in the branch

* changed from route decorator to in method check and changed unittests to fail as the default config is to deny the request

* added one test for security_tests

* rebumped the security tests

* utils(config/schema): updating schema for validation on `/api/v1/superuser/config` endpoint (PROJQUAY-4559) (quay#3255)

* initial checkin of schema update

* finished first iteration

* re-added the comments that got lost with json to python dict conversion

* fixed space on comments

* fixed comments

* repush for checks

* black fix

* fixed typos in schema

* separating encryption logic and minor fixes

---------

Co-authored-by: sivaramsingana <47631665+sivaramsingana@users.noreply.github.com>
Co-authored-by: Ivan Bazulic <ibazulic@redhat.com>
Co-authored-by: Kotakonda Sai Deekshith <kdeekshithsai7373@gmail.com>
Co-authored-by: Shubhra Deshpande <shubhrajayant+github@gmail.com>
Co-authored-by: shudeshp <shudeshp@redhat.com>
Co-authored-by: Marcus Kok <47163063+Marcusk19@users.noreply.github.com>
Co-authored-by: Michaela Lang <94735640+michaelalang@users.noreply.github.com>
Co-authored-by: Kenny Lee Sin Cheong <2530351+kleesc@users.noreply.github.com>
Co-authored-by: bcaton <bcaton@redhat.com>
Co-authored-by: Jonathan King <jonathankingfc@gmail.com>
Co-authored-by: Mathieu Bouchard <83231959+bouchardmathieu-qc@users.noreply.github.com>
Co-authored-by: sayalibhavsar <36536724+sayalibhavsar@users.noreply.github.com>
Co-authored-by: Syed Ahmed <syed@apache.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants