SaaS

Secure JFrog Artifactory with cnspec

Scan JFrog Artifactory instances against security and compliance best practices with cnspec.

Artifactory is often the only registry a platform installs from, so a permission mistake there reaches every consumer of the artifacts it serves. cnspec evaluates which repositories exist and what a remote one proxies, which principals hold which actions on which repository patterns, whether an unauthenticated caller can read or publish, who holds administrative rights, which access tokens never expire, every way an account can be created through an identity integration, and where the instance writes its scheduled exports.

If you're new to cnspec, start with the Quickstart. For an overview of every SaaS service cnspec can scan, see the SaaS scanning overview.

Prerequisites

To scan Artifactory with cnspec, you must have:

Authenticate

cnspec connects to Artifactory with an access token. To create one, open the JFrog platform and go to User Menu > Edit Profile > Generate an Identity Token. You can also create one with the JFrog CLI:

jf atc

Administrative rights

Most of what cnspec reads from Artifactory is available only to an administrator:

ResourceRights needed
repositories, systemAny account
permissionTargetsAdministrator
users, groupsAdministrator
security, and the identity integrations under itAdministrator
backupsAdministrator
cleanupPoliciesAdministrator, and a product version that serves the endpoint
accessTokensAdministrator to see every token, otherwise only the caller's own

A token that lacks those rights reports the field as an error rather than as an empty result, so an audit never passes on data cnspec was never allowed to read. If you want full coverage, issue the token from an administrator account.

Instance URL

Pass the JFrog platform base URL. A cloud instance is https://YOUR_NAME.jfrog.io and a self-hosted instance is the host the platform is served on. cnspec accepts the /artifactory suffix that the web interface shows and removes it for you. A URL can also be passed as a bare argument instead of with the --url flag:

cnspec shell artifactory https://example.jfrog.io --token YOUR_TOKEN

Environment variables

You can export the connection details once and reuse them across commands:

export ARTIFACTORY_URL=https://example.jfrog.io
export ARTIFACTORY_TOKEN=YOUR_TOKEN

cnspec also accepts JFROG_URL for the URL, JFROG_ACCESS_TOKEN for the token, and ARTIFACTORY_API_KEY for the legacy API key. When these are set, you can omit the matching flags from the commands below.

Connection options

OptionDescription
--urlJFrog platform base URL, for example https://example.jfrog.io
--tokenArtifactory access token for authentication
--api-keyArtifactory API key, an alternative to the access token

Verify with a quick Artifactory check

Confirm that cnspec can reach your instance by opening a cnspec shell:

cnspec shell artifactory --url https://example.jfrog.io --token YOUR_TOKEN
cnspec> artifactory.system { version serviceId addons }
artifactory.system: {
  version: "7.90.10"
  serviceId: "jfrt@01ab2c3d4e5f6g7h8i9j0k"
  addons: [
    0: "build"
    1: "docker"
  ]
}

If cnspec connects and reports your version, you're ready to scan.

Scan Artifactory

cnspec scan artifactory --url https://example.jfrog.io --token YOUR_TOKEN

When a scan completes, cnspec prints a summary of all the checks it ran, grouped by policy, along with a risk score from 0 (no risk) to 100 (highest risk). Failed checks include remediation guidance to help you fix issues. To learn more about reading scan results, read Understand cnspec Results.

Mondoo doesn't yet ship an out-of-the-box Artifactory policy, so use the checks below as a starting point and create your own policies to meet your specific requirements.

Explore and test checks interactively

Open a cnspec shell to discover resources and try out checks:

cnspec shell artifactory --url https://example.jfrog.io --token YOUR_TOKEN

List all repositories

cnspec> artifactory.repositories { key type packageType blackedOut xrayIndex }
artifactory.repositories: [
  0: {
    key: "docker-local"
    type: "local"
    packageType: "docker"
    blackedOut: false
    xrayIndex: true
  }
]

Find repositories an unauthenticated caller can use

anonymousActions lists what the anonymous user holds on the repository, so an empty list means anonymous callers get nothing:

cnspec> artifactory.repositories.where(anonymousActions.length > 0) { key packageType anonymousActions }

Review the instance security settings

cnspec> artifactory.security {
    anonymousAccessEnabled
    anonymousCanRead
    anonymousCanDeploy
    hideUnauthorizedResources
    userLockPolicyEnabled
    loginAttempts
    passwordExpiryEnabled
    buildGlobalBasicReadAllowed
    buildGlobalBasicReadForAnonymous
  }

List administrators

cnspec> artifactory.users.where(admin == true) { name email realm lastLoggedIn }

Inspect permission targets and who they name

A permission target grants principals actions over repository path patterns, and principals flattens every principal the target names across all of its scopes:

cnspec> artifactory.permissionTargets { name principals { name type actions canDeploy canManage } }

Find grants that cover every repository

A scope with appliesToAllRepositories uses a wildcard key, so it reaches repositories that didn't exist when the grant was written:

cnspec> artifactory.permissionTargets.where(repo.appliesToAllRepositories == true) { name repo { includePatterns } }

List access tokens and what they carry

cnspec> artifactory.accessTokens { id subject grantsAdmin expires expiry refreshable }

Review the identity integrations

Every enabled integration is a separate way to become a principal on the instance, so the set of them bounds who can sign in at all. saml, oauth, httpSso, and crowd return null when the instance has no such configuration:

cnspec> artifactory.security.ldapSettings { key enabled ldapUrl usesEncryptedTransport autoCreateUser }
cnspec> artifactory.security.saml { enabled noAutoUserCreation autoRedirect verifyAudienceRestriction hasCertificate syncGroups }
cnspec> artifactory.security.oauth { enabled persistUsers providers { name providerType enabled } }
cnspec> artifactory.security.httpSso { httpSsoProxied remoteUserRequestVariable noAutoUserCreation }
cnspec> artifactory.security.crowd { enabled serverUrl noAutoUserCreation sessionValidationInterval }

Review which directory groups map onto instance groups

cnspec> artifactory.security.ldapGroupSettings { name strategy groupBaseDn subTree ldapSetting { key enabled } }

Review scheduled exports

An export contains the artifacts and the configuration, including the secrets held in it, so where it's written and how long it's kept are part of the instance's exposure:

cnspec> artifactory.backups { key enabled cronExpression retentionPeriodHours excludeNewRepositories excludedRepositories }

Review cleanup policies

cnspec> artifactory.cleanupPolicies { key enabled cronExpression repositories keepLastNVersions skipTrashcan }

Walk from a resource to its neighbors

Repositories, users, groups, and permission targets reference each other, so a query can start from any of them and walk in either direction:

cnspec> artifactory.repositories { key permissionTargets { name } }
cnspec> artifactory.groups { name users { name admin } }
cnspec> artifactory.users { name permissionTargets { name } }
cnspec> artifactory.accessTokens { id subjectRef { name admin } }

Example security checks

Ensure anonymous access is disabled

cnspec> artifactory.security.anonymousAccessEnabled == false
[ok] value: true

Ensure an unauthenticated caller can't publish artifacts

Anonymous publish rights let anyone replace the artifacts your platform installs:

cnspec> artifactory.security.anonymousCanDeploy == false
[ok] value: true

Ensure no permission target grants anonymous publish

The instance-wide setting isn't the only way anonymous publish gets in. A permission target can name the anonymous user directly, and cnspec lists the targets that would apply whether or not anonymous access is on, so you can see what turning it on would immediately allow:

cnspec> artifactory.security.anonymousDeployTargets.length == 0
[ok] value: true

Ensure no repository exposes actions to the anonymous user

cnspec> artifactory.repositories.all(anonymousActions.length == 0)
[ok] value: true

Ensure build info isn't readable instance-wide

A global build read bypasses the permission targets, so a build that names its artifacts and its environment becomes visible to every account:

cnspec> artifactory.security.buildGlobalBasicReadAllowed == false
[ok] value: true

Ensure build info isn't readable without an account at all

cnspec> artifactory.security.buildGlobalBasicReadForAnonymous == false
[ok] value: true

Ensure accounts lock after repeated failed sign-ins

cnspec> artifactory.security.userLockPolicyEnabled == true
[ok] value: true

Ensure the lockout threshold is strict

cnspec> artifactory.security.loginAttempts <= 5
[ok] value: true

Ensure internal passwords expire

cnspec> artifactory.security.passwordExpiryEnabled == true
[ok] value: true

Ensure administrative rights stay rare

Every administrator can rewrite any grant on the instance, so the count belongs under review:

cnspec> artifactory.users.where(admin == true).length <= 3
[ok] value: true

Ensure no group hands out administrative rights automatically

A group that is both autoJoin and adminPrivileges makes every new account an administrator:

cnspec> artifactory.groups.none(autoJoin == true && adminPrivileges == true)
[ok] value: true

Ensure no administrative token lives forever

cnspec> artifactory.accessTokens.none(grantsAdmin == true && expires == false)
[ok] value: true

Ensure every access token expires

cnspec> artifactory.accessTokens.all(expires == true)
[ok] value: true

Ensure every LDAP server is reached over an encrypted connection

A URL with the ldap scheme rather than ldaps carries the bind credential over an unencrypted connection:

cnspec> artifactory.security.ldapSettings.where(enabled == true).all(usesEncryptedTransport == true)
[ok] value: true

Ensure a directory bind can't create accounts on the instance

When a successful bind creates an account, every principal in the directory becomes a principal here:

cnspec> artifactory.security.ldapSettings.where(enabled == true).none(autoCreateUser == true)
[ok] value: true

Ensure a SAML assertion can't create an account

Run this where the instance uses SAML. When noAutoUserCreation is false, every principal the identity provider accepts becomes a principal here:

cnspec> artifactory.security.saml.noAutoUserCreation == true
[ok] value: true

Ensure a SAML assertion minted for another service can't be replayed

Without the audience restriction check, an assertion issued for a different service provider is accepted here:

cnspec> artifactory.security.saml.verifyAudienceRestriction == true
[ok] value: true

Ensure the SAML identity provider has a signing certificate

Without one, an assertion can't be verified against the provider that claims to have issued it:

cnspec> artifactory.security.saml.hasCertificate == true
[ok] value: true

Ensure an OAuth sign-in can't create an account

cnspec> artifactory.security.oauth.persistUsers == false
[ok] value: true

Ensure a proxy header can't create the account it names

HTTP SSO takes the account name from a request header, so the instance is only as strong as the proxy in front of it. A caller that reaches the instance directly can set the header itself:

cnspec> artifactory.security.httpSso.noAutoUserCreation == true
[ok] value: true

Ensure every repository is indexed for vulnerabilities

cnspec> artifactory.repositories.all(xrayIndex == true)
[ok] value: true

Ensure remote repositories don't leak credentials

allowAnyHostAuth lets a remote repository send its credentials to any host it gets redirected to:

cnspec> artifactory.repositories.all(allowAnyHostAuth == false)
[ok] value: true

Ensure remote repositories can't resolve dependencies from anywhere

cnspec> artifactory.repositories.all(externalDependenciesEnabled == false)
[ok] value: true

Ensure a caller learns nothing about what it may not read

cnspec> artifactory.security.hideUnauthorizedResources == true
[ok] value: true

Ensure no export is kept forever

An export that's kept forever accumulates every secret the instance held at each point in time:

cnspec> artifactory.backups.where(enabled == true).all(retentionPeriodHours != null)
[ok] value: true

Ensure a new repository isn't silently left out of the export

cnspec> artifactory.backups.where(enabled == true).none(excludeNewRepositories == true)
[ok] value: true

Review accounts that aren't active

An account left in a locked or disabled state still holds its group memberships and grants:

cnspec> artifactory.users.where(status != "enabled") { name status realm lastLoggedIn }

Learn more

On this page

PrerequisitesAuthenticateAdministrative rightsInstance URLEnvironment variablesConnection optionsVerify with a quick Artifactory checkScan ArtifactoryExplore and test checks interactivelyList all repositoriesFind repositories an unauthenticated caller can useReview the instance security settingsList administratorsInspect permission targets and who they nameFind grants that cover every repositoryList access tokens and what they carryReview the identity integrationsReview which directory groups map onto instance groupsReview scheduled exportsReview cleanup policiesWalk from a resource to its neighborsExample security checksEnsure anonymous access is disabledEnsure an unauthenticated caller can't publish artifactsEnsure no permission target grants anonymous publishEnsure no repository exposes actions to the anonymous userEnsure build info isn't readable instance-wideEnsure build info isn't readable without an account at allEnsure accounts lock after repeated failed sign-insEnsure the lockout threshold is strictEnsure internal passwords expireEnsure administrative rights stay rareEnsure no group hands out administrative rights automaticallyEnsure no administrative token lives foreverEnsure every access token expiresEnsure every LDAP server is reached over an encrypted connectionEnsure a directory bind can't create accounts on the instanceEnsure a SAML assertion can't create an accountEnsure a SAML assertion minted for another service can't be replayedEnsure the SAML identity provider has a signing certificateEnsure an OAuth sign-in can't create an accountEnsure a proxy header can't create the account it namesEnsure every repository is indexed for vulnerabilitiesEnsure remote repositories don't leak credentialsEnsure remote repositories can't resolve dependencies from anywhereEnsure a caller learns nothing about what it may not readEnsure no export is kept foreverEnsure a new repository isn't silently left out of the exportReview accounts that aren't activeLearn more