Security best practices

DevSecOps

Embrace DevSecOps: Implement Zero Trust principles to fortify your DevOps platform, safeguard your development environment, and integrate Zero Trust seamlessly into your developer workflows.


Note

In the context of CI/CD, implementing least privilege access can be counterproductive due to the dynamic nature of architecture. Each time a new service gets introduced, permissions must be updated beforehand. Additionally, rollbacks might require extra permissions that need to be considered. This challenge is magnified in environments with multiple pipelines. While least privilege permissions aim to minimize the impact of security breaches, it's crucial to balance security with productivity. You can achieve this balance by adopting more permissive access and mitigating the associated risks with compensating controls and security practices outlined on this page.

  • Disable inheritance: Whenever possible, disable inheritance. Inheritance can inadvertently grant access or permissions to unexpected users due to its allow-by-default nature. For more information, see the section on permission inheritance
  • Environment segmentation: Allocate separate Azure accounts for Development, Testing, Production, and other environments. This approach enhances security by minimizing the blast radius and prevents resource conflicts and data contamination. Additionally, it enables multiple ephemeral, feature-specific resources within the development account. For large organizations, consider allocating at least one account per team per environment. Separate accounts for business-critical workloads may also be warranted. Consider adopting Azure Landing Zone for streamlined provisioning and management.
  • Access control and compliance: Leverage Azure Policy in Management Groups to restrict access to unused Azure regions and services, ensuring compliance with organizational standards.
  • Attribute-Based Access Control (ABAC): Implement ABAC with properly tagged resources to limit rogue actor access and prevent unauthorized resource creation.

For more information about permissions, see the following articles:

Project-level permissions

  • Limit access to projects and repos: Reduce the risk of leaking sensitive information and deploying insecure code by limiting access to projects and repositories. Use built-in or custom security groups manage permissions.
  • Disable “Allow public projects”: In your organization’s policy settings, disable the option to create public projects. Switch project visibility from public to private as needed. Users who haven’t signed in have read-only access to public projects, while signed-in users can be granted access to private projects and make permitted changes.
  • Restrict project creation: Prevent users from creating new projects to maintain control over your environment.

Just-in-time access for admin groups

If you have Project Collection Administrator and Project Administrator access, you can modify the configuration of your organization or project. To enhance security for these built-in administrator groups, consider implementing just-in-time access using a Microsoft Entra Privileged Identity Management (PIM) group. This approach allows you to grant elevated permissions only when needed, reducing the risk associated with permanent access.

Configure access

  1. Create a role-assignable group in Microsoft Entra ID.
  2. Add your Microsoft Entra group to the Azure DevOps group.

Note

When you configure just-in-time access using a Microsoft Entra Privileged Identity Management (PIM) group, ensure that any user with elevated access also retains standard access to the organization. This way, they can view the necessary pages and refresh their permissions as needed.

Use access

  1. Activate your access.
  2. Refresh your permissions in Azure DevOps.
  3. Take the action requiring administrator access.

Note

Users have elevated access in Azure DevOps for up to 1 hour after their PIM group access gets deactivated.

Use PATs sparingly

  • Scope PATs to specific roles: Assign PATs only the necessary permissions for specific tasks. Avoid granting global access to multiple organizations or repositories to minimize the risk of accidental misuse.
  • Avoid write or manage permissions on builds and releases: PATs shouldn't have write or manage permissions on builds, releases, or other critical resources. Restricting these permissions helps prevent unintended actions that could affect your pipelines or deployments.
  • Set expiration dates and keep PATs secret: Always set an expiration date for PATs. Regularly review and renew them as needed. Treat PATs as critical as passwords, keeping them confidential and avoiding public sharing or hardcoding in application code.
  • Avoid hardcoding PATs in application code: Instead of embedding PATs directly in your code, use secure configuration files or environment variables to store and retrieve PATs dynamically. dynamically.
  • Audit and revoke unused PATs regularly: Administrators should periodically review all PATs using the REST APIs. Revoke any PATs that are no longer needed or don’t meet the recommended criteria.

For more information, see Manage PATs with policies - for administrators and Use PATs.


Policies

  • Require external reviewers: Ensure at least one reviewer outside the original requester for a thorough review process. The approver shares co-ownership of the changes and accountability for any potential effects.
  • Require CI build to pass: Establish a baseline for code quality by requiring the Continuous Integration (CI) build to pass before merging a PR. CI checks include code linting, unit tests, and security scans.
  • Disallow self-approval: Prevent the original PR requester from approving their own changes to ensure an unbiased review process and avoid conflicts of interest.
  • Disallow PR completion with “wait” or “reject” votes: Prevent PR completion even if some reviewers vote to wait or reject, encouraging addressing all feedback before merging.
  • Reset reviewer votes on changes: Reset reviewer votes when recent changes are pushed to the PR to ensure reviewers reevaluate the updated code.
  • Lock down release pipelines: Limit release pipelines to specific branches (usually production or main) to avoid accidental deployments from other branches.
  • Enforce settable variables at queue time: Enable the "Enforce settable at queue time" option for pipeline variables to prevent users from overriding variable values during pipeline execution.
  • Disallow variable overrides in the editor: For variables set in the pipeline editor, disallow user overrides to maintain consistency and prevent unintended changes.

Agents

  • Grant permissions sparingly: Limit permissions to the smallest necessary set of accounts to reduce the attack surface.
  • Configure restrictive firewalls for agents: Set up firewalls to be as restrictive as possible while still allowing agents to function, balancing security and usability.
  • Regularly update agent pools: Keep your agent fleet up-to-date by regularly updating agents to ensure vulnerable code isn’t running, reducing the risk of exploitation.
  • Use a separate agent pool for production artifacts: Isolate production artifacts by using a distinct agent pool, preventing accidental deployments from nonproduction branches.
  • Segment sensitive pools: Create separate pools for sensitive and nonsensitive workloads. Only allow credentials in build definitions associated with the appropriate pool.

Definitions

  • Use YAML for pipeline definitions: Define pipelines using YAML (Yet Another Markup Language) for better traceability and adherence to approval guidelines and version control practices.
  • Restrict edit access to pipeline definitions: Limit access to editing pipeline definitions to the minimum necessary accounts to reduce the risk of accidental or unauthorized changes.

Input

  • Include checks for variables in build scripts: Implement checks within your build scripts to mitigate potential command injection attacks through settable variables. Validate input values and prevent unintended or malicious commands.
  • Limit “settable at release time” build variables: Minimize the number of build variables settable at release time to reduce the attack surface and simplify configuration management.

Tasks

  • Avoid remotely fetched resources: Whenever possible, avoid fetching resources from external URLs during your build process. If remote resources are necessary, use versioning and hash checking to ensure integrity.
  • Avoid logging secrets: Never log sensitive information, such as secrets or credentials, in your build logs to prevent unintentional exposure and compromise of security.
  • Use Azure Key Vault for secrets: Instead of storing secrets directly in pipeline variables, use Azure Key Vault to manage and retrieve secrets securely.
  • Restrict running builds against arbitrary branches or tags: For security-critical pipelines, limit users from running builds against any branch or tag. Define specific authorized branches or tags to prevent accidental or unauthorized executions.
  • Disable inheritance for pipeline permissions: Inherited permissions can be overly broad and might not accurately reflect your needs. Disable inheritance and set permissions explicitly to align with your security requirements.
  • Limit job authorization scopes: Always restrict job authorization scopes to the minimum necessary. Fine-tune permissions based on the specific tasks performed by each job.

Repositories and branches

  • Require a minimum number of reviewers: Enable the policy to ensure every pull request receives reviews from at least two approvers, promoting thorough code review and accountability.
  • Configure repository-specific security policies: Tailor security policies to each repository or branch instead of project-wide policies to reduce risk, enforce change management standards, and enhance code quality.
  • Isolate production secrets in a separate key vault: Store production secrets separately in an Azure Key Vault and limit access to a need-to-know basis to maintain separation from nonproduction builds.
  • Segregate test environments from production: Avoid mixing test environments with production to ensure credentials and secrets aren't used in nonproduction contexts.
  • Disable forking: Disabling forking helps manage security by preventing the proliferation of forks, making it easier to track security across all copies.
  • Avoid providing secrets to fork builds: Refrain from sharing secrets with forked builds to keep them confidential and not exposed to forks.
  • Consider manually triggering fork builds: Manually trigger builds for forks rather than allowing automatic triggers to provide better control over security checks.
  • Use Microsoft-hosted agents for fork builds: Use Microsoft-hosted agents for forked builds as they're maintained and secure.
  • Scan production build definitions in Git repositories: Regularly check production build definitions stored in your project’s Git repository for any credentials or sensitive information.
  • Configure branch control checks for production context: Set up branch control checks to restrict the use of sensitive connections (for example, prod-connection) to pipelines running in the context of the production branch, ensuring proper authorization and preventing accidental misuse.

For more information, see Other security considerations.

Containers

  • Use trusted images: Utilize official and verified images from reputable sources such as Azure Container Registry or Docker Hub. Always specify a specific version or tag to maintain consistency and reliability, rather than relying on the latest tag. Regularly update base images to include the latest security patches and bug fixes.
  • Scan containers for vulnerabilities and enforce runtime threat protection: Use tools such as Microsoft Defender for Cloud to monitor and detect security risks. Additionally, Azure Container Registry offers integrated vulnerability scanning to help ensure container images are secure before deployment. You can also integrate third-party scanning tools through Azure DevOps extensions for added security checks.
  • Implement security policies to prevent privilege escalation and ensure containers run with the least amount of privileges necessary: For example, Azure Kubernetes Service (AKS), role-based access control, and Pod Security Admission allow you to enforce policies that restrict container privileges, ensure non-root execution, and limit access to critical resources. Define Pod Security Admission policies (for Kubernetes 1.22 and later) to apply restrictions, such as preventing the use of privileged containers or ensuring containers do not access the host network.
  • Utilize Network Policies: Network Policies can be used to restrict communication between containers, ensuring that only authorized containers can access sensitive resources within your network. In addition, Azure Policy for AKS can be leveraged to enforce container security best practices, such as ensuring only trusted container images are deployed.
  • Set container-specific resource limits: For example, set limits on CPU and memory to prevent containers from consuming excessive resources, which could lead to denial of service or security vulnerabilities.

Secure GitHub Integrations

  • Use OAuth flow instead of PATs: Disable PAT-based authentication for GitHub service connections and opt for OAuth flow for better security and integration.
  • Avoid admin or owner identities: Never authenticate GitHub service connections using an identity that's an administrator or owner of any repositories. Limit permissions to the necessary level.
  • Avoid full-scope GitHub PATs: Refrain from using a full-scope GitHub PAT to authenticate service connections. Use tokens with the minimum required permissions.
  • Avoid personal GitHub accounts as service connections: Don’t use personal GitHub accounts as service connections in Azure DevOps. Instead, create dedicated service accounts or use organization-level accounts.