1 - Database Storage
Anchore Enterprise stores all metadata in a structured format in a PostgreSQL database to support API operations and searches.
Examples of data persisted in the database:
- Image metadata (distro, version, layer counts, …)
- Image digests to tag mapping (docker.io/nginx:latest is hash sha256:abcd at time t)
- Image analysis content indexed for policy evaluation (files, packages, ..)
- Feed data
- vulnerability info
- package info from upstream (gem/npm)
- Accounts, users…
- …
If the object store is not explicitly set to an external provider, then that data is also persisted in
the database but can be migrated
Reducing Database Storage Usage
Beyond enabling a non-DB object store there are some configuration
options to reduce database storage and IO used by Anchore Enterprise.
Configuration of Indexed DB Storage for Package DB File Entries
There is a configuration option for the policy engine service to disable the usage of
the database for storing indexed package database entries from each analyzed image. This data represents the files in
each distro package and their metadata (digests and permissions) from each scanned image in the image_package_db_entries table.
That table is only used by the policy engine to deliver the policy trigger [‘packages.verify’],
but if you do not use that trigger then the use of the storage can be disabled thereby reducing database load and resource usage.
The data can be quite large, often in the thousands of rows per analyzed image, so for some customers that do not use this
data for policy, disabling the loading of this data can reduce database consumption significantly.
Disabling Indexed DB Storage for Package DB File Entries
In each policy engine’s config.yaml file, change:
enable_package_db_load: true
to
enable_package_db_load: false
You can configure the enable_package_db_load property, which is enabled (true) by default, through any of the usual mechanisms:
- Helm: set
anchoreConfig.policy_engine.enable_package_db_load in your values file. In v6.x the chart no longer accepts this as the ANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOAD environment variable via extraEnv. - Docker Compose: set the
ANCHORE_POLICY_ENGINE_ENABLE_PACKAGE_DB_LOAD environment variable on the policy engine service. - API: if API-driven configuration is enabled, set it at runtime through the API, as with many other settings.
Note that disabling the table usage will also disable support for the packages.verify trigger and any policies that have the
trigger in a rule will be considered invalid and return errors on evaluation. Any new policies that attempt to use the trigger
will be rejected on upload as invalid if the trigger is included.
Once this configuration is set, you may delete data in that db table to reclaim some database storage capacity. If
you’re interested in this option please contact support for guidance on this process.
Enabling Indexed DB Storage for Package DB File Entries
If you find that you do need the trigger, you can change the configuration to use the table then support will be
restored. However, any images analyzed while the setting was ‘false’ will need to be re-analyzed in order to
populate their data in that table correctly.
Limit pg_cron Logging
Anchore Enterprise v6 runs its job-framework maintenance tasks on a schedule using the PostgreSQL pg_cron extension. These tasks run continuously whether or not the deployment is busy: the most frequent runs every 2 seconds, and together they run about 70 times a minute. With pg_cron’s default settings, every one of those runs is logged twice:
cron.log_run (default on) adds a row to the cron.job_run_details table for each run. Neither pg_cron nor Anchore Enterprise ever removes these rows, so the table grows until the database volume fills. On one deployment, 55 days of history had reached 22 GB, almost a quarter of the database. The table is also included in every pg_dump backup.cron.log_statement (default on) writes the full SQL text of each job to the PostgreSQL server log before it runs. Several of these jobs are many kilobytes long, which adds up to about 500 MB of log output a day on an otherwise idle deployment. In Docker Compose and Kubernetes this goes to the database container’s standard output, and from there into whatever collects your container logs. The provided Docker Compose file caps each container’s log at 100 MB, so this output does not fill the disk, but it makes the database log rotate every few hours, discarding PostgreSQL’s other messages with it.
Anchore recommends turning both settings off. Anchore Enterprise does not read cron.job_run_details, so nothing in the product depends on it. A job that fails is still written to the PostgreSQL server log, with the error and the statement that failed. What you give up is the record of runs that succeeded.
If a service exits with FATAL: pg_cron is not firing maintenance jobs, the message suggests inspecting cron.job_run_details. With cron.log_run off that table stays empty, so check the PostgreSQL server log for the failure instead.
Both settings take effect only when PostgreSQL restarts. Reloading the configuration is not enough.
Docker Compose
The anchore-db service in the provided Docker Compose file sets both. If your docker-compose.yaml predates this, add -c cron.log_run=off -c cron.log_statement=off to the end of the anchore-db service’s command, keeping any other options you have set:
command: postgres -c shared_preload_libraries=pg_stat_statements,pg_cron -c pg_stat_statements.track=all -c max_connections=2000 -c shared_buffers=128MB -c cron.use_background_workers=on -c work_mem=8MB -c cron.log_run=off -c cron.log_statement=off
Then recreate the database container so PostgreSQL restarts with the new settings:
docker compose up -d anchore-db
CloudNativePG
The cluster in Run PostgreSQL In-Cluster with CloudNativePG sets both. For a cluster created before this, add them under spec.postgresql.parameters and re-apply the manifest:
spec:
postgresql:
parameters:
"cron.log_run": "off"
"cron.log_statement": "off"
kubectl apply -f cnpg-cluster.yaml
The operator restarts the PostgreSQL instances to apply the change.
Managed PostgreSQL Services
Set cron.log_run and cron.log_statement to off in the Amazon RDS DB parameter group, the Google Cloud SQL database flags, or the Azure Database for PostgreSQL server parameters, then restart the instance.
Keep Run History with a Retention Job
If you want to keep recent run history, leave cron.log_run on, turn only cron.log_statement off, and schedule a job that deletes old history. Connect to the database named in cron.database_name as the database user Anchore Enterprise uses, and run:
SELECT cron.schedule(
'purge-cron-run-history',
'0 * * * *',
$$DELETE FROM cron.job_run_details WHERE end_time < now() - interval '1 day'$$
);
This keeps one day of history, pruned every hour. pg_cron only lets a user who is not a superuser delete history for jobs that user owns, which is why the job is scheduled as the Anchore Enterprise database user. Anchore Enterprise upgrades reinstall only Anchore’s own maintenance jobs, so this job is kept across upgrades.
Reclaim Space Already Used
Turning cron.log_run off stops the table from growing but does not remove history already recorded. To remove it and return the space to the operating system immediately, truncate the table as a database superuser:
Docker Compose:
docker compose exec anchore-db psql -U postgres -c "TRUNCATE cron.job_run_details;"
CloudNativePG (run against the primary instance):
kubectl exec -it anchore-pg-1 -n anchore -- \
psql -U postgres -d anchore -c "TRUNCATE cron.job_run_details;"
Managed PostgreSQL services: connect to the database named in cron.database_name as the administrative user that created the pg_cron extension, and run TRUNCATE cron.job_run_details;. If your provider does not allow that user to truncate the table, run DELETE FROM cron.job_run_details; instead.
TRUNCATE is instant and safe to run while Anchore Enterprise is running. A DELETE of the same rows is slow on a table this size, and the space it frees stays allocated to the table for PostgreSQL to reuse, rather than being returned to the operating system.
Encrypting Database Secrets at Rest
Starting with v6.1, Anchore Enterprise can encrypt sensitive columns in the database at rest using AES-256-GCM. The encryption is transparent to all users.
Encryption is off by default. A deployment that configures no encryption key stores these columns as plaintext, exactly as releases before v6.1 did, and starts normally with no additional configuration. Encryption is enabled by supplying a key - see Delivering the Keyring. Anchore recommends enabling it.
You can opt in whenever you are ready; it does not have to be at install time. Existing plaintext values stay readable after you add a key, and rotate seals them under it. Note that opting in is effectively one-way: once a deployment has encrypted data, it will not start again without its keyring (see Once Encryption Is Enabled).
What Is Encrypted
Database stored credential materials are encrypted at rest, this includes registry access information, notifications authentication material and others.
Threat Model
Protects against: leaked database backups, lost replica disks, casual DBA access, exfiltrated pg_dump output.
Does not protect against: a compromised service host (the keyring lives in process memory by necessity), or an attacker who can read wherever the operator stores the keyring source.
The deployer is responsible for protecting the keyring source. The implementation is the same across all platforms, if you choose to use a service like AWS Secrets Manager you are still responsible for keeping a secure backup copy of the key data.
Every encryption key is 32 random bytes, base64url-encoded. Each keyring entry is just that bare base64url string.
The first entry in the keyring is primary (used for new writes); the second, if present, is decrypt-only and retained only while a rotation is in flight. Primacy is position based, not value based - the first entry is always the primary.
A keyring holds at most two keys - the primary plus one retained previous key. The service refuses to start with more. This forces each rotation to complete (rotate, verify, drop the old key) before the next one begins, and stops retired generations accumulating in the keyring.
A key’s bytes must never change once it has encrypted data, and a retired key must never be reintroduced as primary. Because the kid (key id) is derived from the key bytes, a kid permanently identifies one specific key - you cannot reuse it.
- No rebind. If a kid is configured whose fingerprint differs from the one previously recorded for it, the service refuses to start. (Restoring a dropped key with its original bytes is always allowed - the fingerprint matches.)
- No rollback. The primary key must not be older (by the timestamp it was first registered) than a key the deployment has since retired. Configuring a superseded key as primary looks like a rollback to a key you have moved past, and the service refuses to start. Restore the most recent key as the primary. Deliberately reverting to an older key is not a recommended action; if you believe you genuinely need to, contact Anchore support.
Minting a Key
A key is simply 32 random bytes, base64url-encoded. Two equivalent methods:
Method 1: The management CLI
anchore-enterprise-manager db encryption generate-key
# Hk7q...base64-32-bytes...==
Place the emitted key in the current-key slot (ANCHORE_DB_ENCRYPTION_KEY_CURRENT); during a rotation, move the existing current key to the previous slot (ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS) and put the new key in current.
Method 2: A Python snippet
For audit-friendly key minting without any Anchore code on the path:
python3 -c "import secrets, base64; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())"
# Hk7q...base64-32-bytes...==
Delivering the Keyring
The keyring lives in the existing config file under credentials.database.encryption, alongside the database credentials the operator already manages there. provider selects the key provider (only static is supported), and keys is a list of bare base64url keys. Giving each slot its own reference - a current key and an optional previous key - lets each map to a distinct entry/version in your secret store, which is what makes managed rotation work (see Rotating a Key).
credentials:
database:
user: ${ANCHORE_DB_USER}
password: ${ANCHORE_DB_PASSWORD}
host: ${ANCHORE_DB_HOST}
port: ${ANCHORE_DB_PORT}
database: ${ANCHORE_DB_NAME}
encryption:
provider: static
keys:
- ${ANCHORE_DB_ENCRYPTION_KEY_CURRENT}
- ${ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS}
ANCHORE_DB_ENCRYPTION_KEY_CURRENT is the primary, used for new writes. ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS is the retained previous key, present only during a rotation. Both slots are optional - a slot that is unset, or set to an empty value, is simply dropped, so the template above is valid whether you have two keys, one, or none.
With neither slot supplied the keyring is empty, which is how encryption stays off: the service starts normally and logs a warning that these columns are being written as plaintext. The one exception is a deployment that has previously used a keyring - it will refuse to start without it, rather than silently downgrade (see Once Encryption Is Enabled).
Keys never appear in service logs, API responses, or configuration-dump output.
Deployment Examples
Docker Compose - one env-var per slot:
services:
enterprise-api:
environment:
ANCHORE_DB_ENCRYPTION_KEY_CURRENT: 'Hk7q...base64-32-bytes...=='
# ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS is set only during a rotation
Kubernetes (Helm) - the Anchore Enterprise Helm chart manages the keyring for you. Supplying a key is what enables encryption; a values file with no key leaves it off. Set the keys in your values file and the chart creates the Kubernetes Secret and projects each slot into the service pods:
anchoreConfig:
database:
encryption:
currentKey: 'Hk7q...base64-32-bytes...=='
# previousKey: '9aZ4...==' # set only during a rotation
To keep key material out of your values file, reference a Secret you manage yourself instead. When existingSecret is set, currentKey/previousKey are ignored:
anchoreConfig:
database:
encryption:
existingSecret: anchore-db-encryption
currentKeySecretKey: current_key
previousKeySecretKey: previous_key # read only while a rotation is in flight
When retiring the previous key after a rotation, delete the key from the Secret rather than setting it to an empty string - either is accepted, but a deleted key is unambiguous.
AWS Secrets Manager - because the key value can be any random 32 bytes, you can let Secrets Manager rotate it for you. Map the secret’s staged versions to the two slots: AWSCURRENT is the current key, AWSPREVIOUS is the previous key that Secrets Manager retains across a rotation. The store itself then supplies the decrypt overlap a rotation needs, with no key-formatting step. The snippet below goes in the secrets array of the container definition in your Amazon ECS task definition (for example, when running Anchore Enterprise on ECS or Fargate):
[
{
"name": "ANCHORE_DB_ENCRYPTION_KEY_CURRENT",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:anchore/db-encryption-key"
},
{
"name": "ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:anchore/db-encryption-key:AWSPREVIOUS::"
}
]
Rotating a Key
A rotation moves the current key into the previous slot, promotes a freshly minted key to current, re-encrypts existing rows under it, then drops the old key. The procedure is identical across any deployment target.
Mint a new key and place the existing current key in the previous slot:
anchore-enterprise-manager db encryption generate-key
# <new-key>
Set ANCHORE_DB_ENCRYPTION_KEY_CURRENT to <new-key> and ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS to the key that was current.
Restart all replicas. New writes are sealed under the new current key; existing rows still decrypt because the previous key is retained.
Run the rotation pass. It rewrites every row sealed under the previous key using the current key. It requires both slots to be configured - it needs the previous key to decrypt and the current key to re-encrypt:
anchore-enterprise-manager db encryption rotate
Then verify no rows remain under a non-current key:
anchore-enterprise-manager db encryption status
# ... Rotation status: complete
Drop the previous key - remove ANCHORE_DB_ENCRYPTION_KEY_PREVIOUS, or set it to an empty value, or let the store age out AWSPREVIOUS, then restart. All three are equivalent: an unset or empty slot is dropped from the keyring. The keyring now holds only the current key.
Dropping the previous key is not optional: because the keyring holds at most two keys, the service refuses to start if you add a new key before dropping the previous one. Each rotation must fully complete - rotate, verify, drop - before the next one can begin. The no-rollback gate prevents accidentally reverting to the dropped key on a later boot.
Encrypting Existing Plaintext
rotate is also how you encrypt values still stored as plaintext - which is the second half of opting in, after running with encryption off and then configuring a keyring. Plaintext needs no previous key (there is nothing to decrypt first), so in this case rotate accepts a single configured key and encrypts the plaintext under it:
anchore-enterprise-manager db encryption rotate
A single-key rotate is refused if encryption has previously been enabled. One key with no plaintext to act on signals a key rotation started without the previous key configured, which would leave the outgoing rows unreadable to the rotation. This same plaintext-to-ciphertext conversion also runs automatically as part of db upgrade.
Checking the Current Keyring
Two read-only commands report on the configured keyring without modifying anything:
anchore-enterprise-manager db encryption check-keyring - parses the configured keyring and reports which kids are present, which is primary, and any validation errors. Does not touch the database. Because a kid is the key’s own fingerprint (the leading hex of the SHA-256 of its bytes), comparing the kids reported here across replicas confirms they all loaded the same keyring - without ever revealing the key material.anchore-enterprise-manager db encryption status - counts the rows in each registered encrypted column by their stored kid (primary, secondary, plaintext, unknown). Useful before and after rotation.
Boot Behaviour
The presence of a keyring is the only switch. There is no separate enable/disable setting:
| Keys configured | Deployment has encrypted before | Result |
|---|
| No | No | Starts normally, encryption off, columns written as plaintext, warning logged at every startup |
| Yes | No | Starts with encryption on, records this key generation, new writes encrypted |
| Yes | Yes | Starts, provided the keyring can still read the stored data |
| No | Yes | Refuses to start - see below |
Running Without Encryption
This is the default state and needs no configuration: supply no keys and the deployment behaves as it did before v6.1, storing these columns as plaintext. A warning naming the affected behaviour is logged on every startup, and anchore-enterprise-manager db encryption check-keyring reports that encryption is off.
We do not recommend running production deployments this way. Because you can enable encryption at any later point without downtime beyond a restart, there is no need to decide at install time.
Once Encryption Is Enabled
The first time a keyring boots, the deployment records that key generation. From that point on, starting without the keyring is refused:
No encryption keys are configured in credentials.database.encryption.keys, but this
deployment has previously registered encryption keys (kids ['c30a71b6ecab']). Starting
without them would store new writes to encrypted columns as PLAINTEXT alongside the
existing encrypted data, and the data already encrypted would be unreadable. Restore
the keyring in credentials.database.encryption.keys before starting the service.
This is deliberate. Booting on would write new secrets in the clear beside unreadable ciphertext, so the failure is loud instead of silent. It is also the error you will see if a key variable goes missing by accident - a Secret key deleted, or a task definition that no longer resolves - so treat it as “the keyring did not arrive”, not as a request to remove encryption. The fix is to restore the keys and restart.
Enabling encryption is permanent. Once the deployment has recorded a key generation, encryption cannot be turned off again: disabling it is not supported, and neither is converting encrypted rows back to plaintext. Treat enabling it as a one-time decision, and make certain the keyring is backed up somewhere you can recover it from before you begin - see
Catastrophic Key Loss.
Catastrophic Key Loss
Loss of the encryption keyring is unrecoverable. Every encrypted value in the affected tables becomes permanently unreadable: registry credentials must be re-entered and notification endpoints must be reconfigured from scratch. Restoring from a database backup will not help unless the keyring at the time of backup is also recoverable.
Treat the keyring as you would a database root-credential backup: store it in your secret-management system, back it up to at least one independent location, and ensure that at least two operators have recovery access.
Pruning Unreadable Rows
anchore-enterprise-manager db encryption prune-rows is the targeted tool for removing only the rows you can no longer read - for example after intentionally dropping a key generation - while keeping everything that is still decryptable. It performs a full decryption pass (like anchore-enterprise-manager db encryption verify) and deletes a row only if one of its encrypted columns actually fails to decrypt with the configured keyring, for any reason: a dropped kid, wrong key bytes, a corrupted ciphertext, or a malformed envelope. Every row that still decrypts under a current key is left untouched.
anchore-enterprise-manager db encryption prune-rows
# This will permanently delete rows that cannot be decrypted with the current keyring (retained kids: 4bb06f8e4e3a, 75877bb41d39). Rows that still decrypt are kept:
# registries: 3 rows
# ...
# To instead delete ALL encrypted rows (e.g. the keyring is lost entirely), use `anchore-enterprise-manager db encryption purge-rows`.
# This action is irreversible. Type 'confirm delete' (case-sensitive) to proceed.
Unlike purge-rows, prune-rows requires a configured keyring - it needs to attempt decryption to decide what is recoverable. If the keyring is lost entirely there is nothing to decrypt against; use the Disaster-Recovery Purge instead.
Like purge, prune is deployment-wide (not scoped to an account), requires the exact confirm delete string to proceed, and records an audit event before any rows are deleted. Run anchore-enterprise-manager db encryption verify first to see exactly which rows fail to decrypt before pruning.
Disaster-Recovery Purge
anchore-enterprise-manager db encryption purge-rows is the last-resort tool when the keyring is unrecoverable. It deletes every row in the registered encrypted-column tables whose encrypted column carries the enc:: prefix - that is, every row whose payload would be unrecoverable. If you still hold keys for some of the data, prefer Pruning Unreadable Rows, which removes only the rows that fail to decrypt and keeps the rest.
anchore-enterprise-manager db encryption purge-rows
# This will permanently delete EVERY row containing encrypted data, whether or not it is still decryptable:
# registries: 24 rows
# notifications_endpoint_configurations: 11 rows
# ...
# If you still hold keys for some of this data, use `anchore-enterprise-manager db encryption prune-rows` instead - it removes only rows under kids missing from the keyring and keeps the rest.
# This action is irreversible. Type 'confirm delete' (case-sensitive) to proceed.
# Any other input - including empty - aborts.
The command requires the operator to type confirm delete exactly (case-sensitive). Any other input - including empty input, alternative capitalisations, or surrounding whitespace - aborts safely. The command operates raw against the database and does not need the keyring to be configured, so it works in the exact scenario where the keyring is unreachable.
What you will lose
Purge removes whole records, not just the encrypted fields, so the loss is operational, not merely cryptographic. After a purge you should expect to re-establish, from scratch, anything that depended on the deleted records:
- Registry connection details - the deployment can no longer authenticate to the affected registries, so any registry whose record was removed must be re-added before its images can be analyzed again.
- Notification endpoint setup - configured destinations stop receiving notifications and must be re-created and re-enabled.
Restoring from a database backup does not recover this data unless the keyring that was in use when the backup was taken is also available. Treat purge as a clean slate for the affected records.
Run anchore-enterprise-manager db encryption status first to see exactly which rows will be deleted before invoking purge.
3.3 - Amazon S3
This page describes configuration when using Amazon S3 for object storage with IAM role authentication.
Anchore strongly recommends using IAM roles for secure access to Amazon S3.
IAM Role Authentication
For Anchore Enterprise to use an AWS IAM role, the environment it runs in (such as an EC2 instance, ECS task, or Kubernetes pod) must have an AWS IAM role with the necessary S3 bucket permissions:
"Action": [
"s3:PutObject*",
"s3:GetObject*",
"s3:DeleteObject*"
]
If using KMS w/ s3 Bucket Key you will need the following as well:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3BucketAccess",
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::myapp-prod-data-bucket"
},
{
"Sid": "S3ObjectAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::myapp-prod-data-bucket/*"
},
{
"Sid": "KMSAccessToSpecificKey",
"Effect": "Allow",
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:us-east-1:123456789012:key/11111111-2222-3333-4444-555555555555"
}
]
}
The KMS key’s IAM resource policy needs to grant the S3 principal access as well. By default KMS keys grant access to the AWS Account’s IAM root which would work in combination with the policy above. Please note that this is meant to serve as a working example as opposed to being the most restrive configuration possible.
Reference: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html?icmpid=docs_amazons3_console
In your values.yaml file storage_driver section, set the iamauto parameter to true:
services:
catalog:
object_store:
storage_driver:
name: 's3'
config:
iamauto: true
With iamauto: true, Anchore Enterprise automatically adopts the IAM role of its host environment. This is the most secure method for granting Amazon S3 access as it removes the need to store credentials such as ACCESS_KEY and SECRET_KEY in configuration files. For docker-compose environments the IAM role will be shared with the instance profile. For Helm deployments there are multiple options for assigning the IAM role to Anchore Enterprise:
- Pod Identity
- IRSA
- EKS Worker Role (least secure)
For Pod Identity and IRSA you can designate the name of the service account via the Helm values file as follows:
serviceAccountName: "anchore-sa" # This should match the name of the service account created in EKS
If using an IAM role to grant access to ECR then you will need to combine the S3 permissions with the ECR permissions in the same IAM role.
Please note that serviceAccountName needs to be set on the OSAAMigrationJob specifically, if running the OSAA migration. In doing so this will give the migration job access to S3 but it will not be able to spin down the Anchore Enterprise pods during the migration.
Other S3 Configuration Options
Below are other configurable parameters for the Anchore Enterprise S3 driver:
The Anchore Enterprise S3 driver supports document compression to reduce storage space. Set to true to enable or false to disable and
min_size_kbytes sets the minimum document size in kilobytes to be compressed.
config:
...
compression:
enabled: true
min_size_kbytes: 100
region - the AWS region of your Amazon S3 bucket. It is required if url is not specified.
bucket - the name of the Amazon S3 bucket for Anchore’s data storage.
create_bucket - if set to true, Anchore Enterprise will attempt to create the bucket if it doesn’t exist. It is, however, recommended to pre-create the bucket.
Example
Here is a full configuration example for the S3 driver using IAM role authentication (config.yaml used in docker-compose):
services:
catalog:
object_store:
verify_content_digests: true
storage_driver:
name: 's3'
config:
# AWS IAM role authentication
iamauto: true
# Amazon S3 bucket configuration
region: 'us-east-1'
bucket: 'my-anchore-data'
create_bucket: false
# Optional compression
compression:
enabled: true
min_size_kbytes: 100
Helm chart values for kubernetes deployment:
anchoreConfig:
catalog:
object_store:
verify_content_digests: true
storage_driver:
name: 's3'
config:
# AWS IAM role authentication
iamauto: true
# Amazon S3 bucket configuration
region: 'us-east-1'
bucket: 'my-anchore-data'
create_bucket: false
# Optional compression
compression:
enabled: true
min_size_kbytes: 100
3.5 - Migrating Data to New Drivers
To migrate data from one driver to another (e.g. DB to S3), Anchore Enterprise includes capabilities that automate the process in the anchore-manager tool packaged with the system. For Helm-based deployments, this is further automated via Helm upgrade helpers, whereas for Docker Compose deployments the tool must be run manually.
The migration process is an offline process; Anchore Enterprise is not designed to handle an online migration.
The object storage migration process migrates any data stored in the source config to the destination configuration, if the analysis archive is configured to use the same storage backend as the primary object store then that data is migrated along with all other data, but if the source or destination configurations define different storage backends for the analysis archive than that which is used by the primary object store, then additional parameters are necessary to indicate which configurations to migrate to/from.
The most common migration patterns are:
- Migrate from a single backend configuration to a split configuration to keep the Active Data Set in the DB and then move the Archive Data Set (analysis archive data) to an external system (db -> db + s3)
- Migrate from a dual-backend configuration to a single-backend configuration with a different config (e.g. db + s3-compatible -> s3-compatible)
At a high-level the process is:
- Shutdown all Anchore Enterprise services and components. The system should be fully offline, but the database must be online and available. For a docker compose install, this is achieved by simply stopping the engine container, but not deleting it.
- Prepare a new
config.yaml that includes the new driver configuration for the destination of the migration (dest-config.yaml) in the same location as the existing config.yaml - Test a new
dest-config.yaml to ensure correct configuration - Run the migration
- Get coffee… this could take a while if you have a lot of analysis data
- When complete, view the results
- Ensure the
dest-config.yaml is in place for all the components as config.yaml - Start Anchore Enterprise services and components.
EXAMPLE: Migration of Object Store in Helm-based Deployment from DB to Amazon S3
The Anchore Enterprise Helm Chart provides a way to run the migration steps listed in this page automatically by spinning up a job and crafting the configs required and running the necessary migration commands. Further information is available via instructions found in our Helm Chart here. Below are example configurations:
Anchore recommends using IAM roles for access to Amazon S3, as in the example below, you can find role configuration details
here.
Example config
osaaMigrationJob:
enabled: true # note that we are enabling the migration job
analysisArchiveMigration:
run: true
bucket: "analysis_archive"
mode: to_analysis_archive
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
iamauto: true
region: <MY_AWS_REGION>
bucket: anchore-analysis-archive
objectStoreMigration:
run: true
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
iamauto: true
region: <MY_AWS_REGION>
bucket: anchore-object-store
# When running the migration you may omit or specify the following which will act as the source of the migration
anchoreConfig:
default_admin_password: foobar
catalog:
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
Once the migration has completed disable the osaaMigrationJob and move the configurations to the anchoreConfig.catalog section.
EXAMPLE: Migration of Object Store in Helm-based Deployment from DB to S3-compatible
Example config
osaaMigrationJob:
enabled: true # note that we are enabling the migration job
analysisArchiveMigration:
run: true # we are specifying to run the analysis_archive migration
bucket: "analysis_archive"
mode: to_analysis_archive
# the deployment will be migrated to use the following configs for catalog.analysis_archive
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
access_key: MY_ACCESS_KEY
secret_key: MY_SECRET_KEY
url: 'https://my-s3-compatible-endpoint.example.com:optional_port'
region: null
bucket: anchore-analysis-archive
objectStoreMigration:
run: true
# note that since this is the same as anchoreConfig.catalog.object_store, the migration
# command for migrating the object store will still run, but it will not do anything as there
# is nothing to be done
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: s3
config:
access_key: MY_ACCESS_KEY
secret_key: MY_SECRET_KEY
url: 'https://my-s3-compatible-endpoint.example.com:optional_port'
region: null
bucket: anchore-object-store
# When running the migration you may omit or specify the following which will act as the source of the migration
anchoreConfig:
default_admin_password: foobar
catalog:
analysis_archive:
enabled: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
object_store:
verify_content_digests: true
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
name: db
config: {}
EXAMPLE: Migration of Object Store in Docker Compose from DB to S3-compatible
The following example demonstrates migration for a Docker Compose deployment.
Preparing for Migration
For the migration process you will need:
- The original
config.yaml used by the services already, if services are split out or using different config.yaml for different services, you need the config.yaml used by the catalog services - An updated
config.yaml (named dest-config.yaml in this example), with the archive driver section of the catalog service config set to the config you want to migrate to - The db connection string from
config.yaml, this is needed by the anchore-manager script directly - Credentials and resources (bucket etc) for the destination of the migration
If Anchore Enterprise is deployed using Docker Compose, the migration must be manually initiated using the anchore-manager script. The following is an example migration for Anchore Enterprise deployed via Docker Compose on a single host with a local postgresql container. This process requires that you run the command in a location that has access to both the source archive driver configuration and the new archive driver configuration.
Step 1: Shutdown all services
All services should be stopped, but the postgresql db must still be available and running. You can use the docker compose stop command and supply all services names except the DB:
docker compose stop anchore-analyzer anchore-api anchore-catalog anchore-policy-engine anchore-queue anchore-enterprise-api-gateway anchore-enterprise-rbac-service redis
Step 2: Prepare a new config.yaml
Both the original and new configurations are needed, so create a copy and update the archive driver section to the configuration you want to migrate to
cd config
cp config.yaml dest-config.yaml
<edit dest-config.yaml>
Step 3: Test the destination config
Assuming that config is dest-config.yaml:
$ docker compose run anchore-catalog /bin/bash
[root@3209ad44d7bb ~]# anchore-manager objectstorage --db-connect ${db} check /config/dest-config.yaml
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB params: {"db_pool_size": 30, "db_connect": "postgresql+pg8000://postgres:postgres.dev@postgres-dev:5432/postgres", "db_connect_args": {"ssl": false, "connect_timeout": 120, "timeout": 30}, "db_pool_max_overflow": 100}
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connection configured: True
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB attempting to connect...
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connected: True
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Using config file /config/dest-config.yaml
[MainThread] [anchore_engine.subsys.object_store.operations/initialize()] [INFO] Archive initialization complete
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Checking existence of test document with user_id = test, bucket = anchorecliconfigtest and archive_id = cliconfigtest
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Creating test document with user_id = test, bucket = anchorecliconfigtest and archive_id = cliconfigtest
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Checking document fetch
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Removing test object
[MainThread] [anchore_manager.cli.objectstorage/check()] [INFO] Archive config check completed successfully
Step 3a: Test the current config.yaml
If you are running the migration for a different location than one of the Anchore Enterprise containers, same as above but using /config/config.yaml as the input to check (skipped in this instance since we’re running the migration from the same container)
Step 4: Run the migration
By default, the migration process will remove data from the source once it has confirmed it has been copied to the destination and the metadata has been updated in the anchore db. To skip the deletion on the source, use the --nodelete option. It is the safest option, but if you use it, you are responsible for removing the data later.
[root@3209ad44d7bb ~]# anchore-manager objectstorage --db-connect ${db} migrate /config/config.yaml /config/dest-config.yaml
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB params: {"db_pool_size": 30, "db_connect": "postgresql+pg8000://postgres:postgres.dev@postgres-dev:5432/postgres", "db_connect_args": {"ssl": false, "connect_timeout": 120, "timeout": 30}, "db_pool_max_overflow": 100}
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connection configured: True
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB attempting to connect...
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connected: True
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] Loading configs
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] Migration from config: {
"storage_driver": {
"config": {},
"name": "db"
},
"compression": {
"enabled": false,
"min_size_kbytes": 100
}
}
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] Migration to config: {
"storage_driver": {
"config": {
"access_key": "9EB92C7W61YPFQ6QLDOU",
"create_bucket": true,
"url": "http://minio-ephemeral-test:9000/",
"region": false,
"bucket": "anchore-engine-testing",
"prefix": "internaltest",
"secret_key": "TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s"
},
"name": "s3"
},
"compression": {
"enabled": true,
"min_size_kbytes": 100
}
}
Performing this operation requires *all* anchore-engine services to be stopped - proceed? (y/N)y
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Initializing migration from {'storage_driver': {'config': {}, 'name': 'db'}, 'compression': {'enabled': False, 'min_size_kbytes': 100}} to {'storage_driver': {'config': {'access_key': '9EB92C7W61YPFQ6QLDOU', 'create_bucket': True, 'url': 'http://minio-ephemeral-test:9000/', 'region': False, 'bucket': 'anchore-engine-testing', 'prefix': 'internaltest', 'secret_key': 'TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s'}, 'name': 's3'}, 'compression': {'enabled': True, 'min_size_kbytes': 100}}
[MainThread] [anchore_engine.subsys.object_store.migration/migration_context()] [INFO] Initializing source object_store: {'storage_driver': {'config': {}, 'name': 'db'}, 'compression': {'enabled': False, 'min_size_kbytes': 100}}
[MainThread] [anchore_engine.subsys.object_store.migration/migration_context()] [INFO] Initializing dest object_store: {'storage_driver': {'config': {'access_key': '9EB92C7W61YPFQ6QLDOU', 'create_bucket': True, 'url': 'http://minio-ephemeral-test:9000/', 'region': False, 'bucket': 'anchore-engine-testing', 'prefix': 'internaltest', 'secret_key': 'TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s'}, 'name': 's3'}, 'compression': {'enabled': True, 'min_size_kbytes': 100}}
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Migration Task Id: 1
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Entering main migration loop
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Migrating 7 documents
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/policy_bundles/2c53a13c-1765-11e8-82ef-23527761d060
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/manifest_data/sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/analysis_data/sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/image_content_data/sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/manifest_data/sha256:a0cd2c88c5cc65499e959ac33c8ebab45f24e6348b48d8c34fd2308fcb0cc138
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/analysis_data/sha256:a0cd2c88c5cc65499e959ac33c8ebab45f24e6348b48d8c34fd2308fcb0cc138
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Deleting document on source after successful migration to destination. Src = db://admin/image_content_data/sha256:a0cd2c88c5cc65499e959ac33c8ebab45f24e6348b48d8c34fd2308fcb0cc138
[MainThread] [anchore_engine.subsys.object_store.migration/initiate_migration()] [INFO] Migration result summary: {"last_state": "running", "executor_id": "3209ad44d7bb:37:139731996518208:", "archive_documents_migrated": 7, "last_updated": "2018-08-15T18:03:52.951364", "online_migration": null, "created_at": "2018-08-15T18:03:52.951354", "migrate_from_driver": "db", "archive_documents_to_migrate": 7, "state": "complete", "migrate_to_driver": "s3", "ended_at": "2018-08-15T18:03:53.720554", "started_at": "2018-08-15T18:03:52.949956", "type": "archivemigrationtask", "id": 1}
[MainThread] [anchore_manager.cli.objectstorage/migrate()] [INFO] After this migration, your anchore-engine config.yaml MUST have the following configuration options added before starting up again:
compression:
enabled: true
min_size_kbytes: 100
storage_driver:
config:
access_key: 9EB92C7W61YPFQ6QLDOU
bucket: anchore-engine-testing
create_bucket: true
prefix: internaltest
region: false
secret_key: TuHo2UbBx+amD3YiCeidy+R3q82MPTPiyd+dlW+s
url: http://minio-ephemeral-test:9000/
name: s3
If something goes wrong you can reverse the parameters of the migrate command to migrate back to the original configuration (e.g. migrate /config/dest-config.yaml /config/config.yaml)
Step 5: Get coffee!
The migration time will depend on the amount of data and the source and destination systems performance.
Step 6: View migration results summary
[root@3209ad44d7bb ~]# anchore-manager objectstorage --db-connect ${db} list-migrations
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB params: {"db_pool_size": 30, "db_connect": "postgresql+pg8000://postgres:postgres.dev@postgres-dev:5432/postgres", "db_connect_args": {"ssl": false, "connect_timeout": 120, "timeout": 30}, "db_pool_max_overflow": 100}
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connection configured: True
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB attempting to connect...
[MainThread] [anchore_manager.cli.utils/connect_database()] [INFO] DB connected: True
id state start time end time from to migrated count total to migrate last updated
1 complete 2018-08-15T18:03:52.949956 2018-08-15T18:03:53.720554 db s3 7 7 2018-08-15T18:03:53.724628
This lists all migrations for the service and the number of objects migrated. If you’ve run multiple migrations you’ll see multiple rows in this response.
Step 7: Replace old config.yaml with updated dest-config.yaml
You should now permanently move into place the new configuration, replacing the old.
[root@3209ad44d7bb ~]# cp /config/config.yaml /config/config.old.yaml
[root@3209ad44d7bb ~]# cp /config/dest-config.yaml /config/config.yaml
Step 8: Restart Anchore Enterprise services
Run the following command at the same location as your docker-compose file to bring all services back up:
docker compose start
The system should now be up and running using the new configuration! You can verify with the anchorectl command by fetching a policy, which will have been migrated:
$anchorectl policy list
✔ Fetched policies
┌─────────────────────────┬──────────────────────────────────────┬────────┬──────────────────────┐
│ NAME │ POLICY ID │ ACTIVE │ UPDATED │
├─────────────────────────┼──────────────────────────────────────┼────────┼──────────────────────┤
│ Default bundle │ 2c53a13c-1765-11e8-82ef-23527761d060 │ true │ 2022-07-14T22:52:27Z │
│ anchore_security_only │ anchore_security_only │ false │ 2022-07-14T22:52:27Z │
│ anchore_cis_1.13.0_base │ anchore_cis_1.13.0_base │ false │ 2022-07-14T22:52:27Z │
└─────────────────────────┴──────────────────────────────────────┴────────┴──────────────────────┘
$ anchorectl -o json-raw policy get 2c53a13c-1765-11e8-82ef-23527761d060
[
{
"blacklisted_images": [],
"comment": "Default bundle",
"id": "2c53a13c-1765-11e8-82ef-23527761d060",
... <lots of json>
If that returns the content properly, then you’re all done!