Introduction

When credential secrets are moved to a new mount location (e.g., migrating from /etc/tower/secrets/ to /var/run/secrets/ or changing Kubernetes secret mount paths), Ansible Tower and running playbooks may continue reading from the old path. This causes authentication failures when the old path is empty or deleted, or worse, uses stale credentials from cached files at the old location, leading to security issues and failed operations.

This commonly occurs during: - Kubernetes secret mount path changes - HashiCorp Vault integration migration - Moving from file-based secrets to environment variables - PaaS platform updates that change secret injection paths - Container orchestration redeployments with new volume mounts

Symptoms

Playbooks fail with authentication errors:

yaml
# Task in playbook
- name: Deploy to production
  aws_s3:
    bucket: myapp-production
    mode: list
  environment:
    AWS_ACCESS_KEY_ID: "{{ lookup('file', '/etc/tower/secrets/aws_access_key') }}"
    AWS_SECRET_ACCESS_KEY: "{{ lookup('file', '/etc/tower/secrets/aws_secret_key') }}"

Error output:

bash
TASK [Deploy to production] ******************************************************
fatal: [localhost]: FAILED! => {
    "msg": "Failed to read file /etc/tower/secrets/aws_access_key: [Errno 2] No such file or directory: '/etc/tower/secrets/aws_access_key'"
}

Or using stale credentials from old location:

bash
TASK [Deploy to production] ******************************************************
fatal: [localhost]: FAILED! => {
    "msg": "An error occurred (InvalidAccessKeyId) when calling the ListObjects operation: The AWS Access Key Id you provided does not exist in our records."
}

In Tower credential logs:

bash
2024-03-15 14:23:45,123 ERROR    awx.main.models.credential Credential lookup failed for credential 42
2024-03-15 14:24:12,456 WARNING  awx.main.tasks Credential path /etc/tower/secrets/vault_token no longer exists
2024-03-15 14:25:01,789 ERROR    awx.main.dispatch Job 12345 failed: unable to inject credentials

In container/pod logs:

```bash # Kubernetes pod events $ kubectl describe pod tower-task-abc123 Events: Warning FailedMount 2m kubelet Unable to attach or mount volumes: unmounted volumes=[secrets], error=paths not found: /etc/tower/secrets

# Container still has old mount $ kubectl exec tower-task-abc123 -- ls -la /etc/tower/secrets/ total 0 ?--------- ? ? ? ? ? aws_access_key # Stale/corrupted entry

# New mount location has correct secrets $ kubectl exec tower-task-abc123 -- ls -la /var/run/secrets/ total 4 -rw-r--r-- 1 root root 40 Mar 15 14:00 aws_access_key -rw-r--r-- 1 root root 40 Mar 15 14:00 aws_secret_key ```

Tower UI shows credential injection errors:

bash
Job 12345 Detail:
Status: Failed
Job Explanation: Credential injection failed: 
  - AWS Credential (ID: 42): File not found at /etc/tower/secrets/aws_access_key
  - Vault Token (ID: 15): Path /etc/tower/secrets/vault_token does not exist

Common Causes

The runtime continues reading from old paths due to several factors:

  1. 1.Credential injection path hardcoded in Tower: Tower's credential type configuration specifies the exact file path for injecting credentials. When secrets are moved, Tower's credential types still reference the old paths.
  2. 2.Environment variables cached in worker processes: Celery workers may have cached environment variables pointing to old secret paths that persist across task executions.
  3. 3.File handle caching: Python's file I/O can cache file handles, especially when credentials are read once at worker startup and reused.
  4. 4.Kubernetes volume mount propagation delay: When Kubernetes changes volume mounts, running containers may not see the change until the pod is restarted, and mount propagation settings affect timing.
  5. 5.Stale symlink or mount point: The old path may exist as a broken symlink or empty directory mount, allowing file operations but returning errors.
  6. 6.Tower credential plugin configuration: Custom credential plugins may have hardcoded paths not updated during the migration.
  7. 7.Ansible module environment inheritance: Ansible modules inherit the environment from the control node, which may have old credential paths set before the move.

Step-by-Step Fix

Step 1: Identify All Credential Path References

Find where old paths are referenced:

```bash OLD_PATH="/etc/tower/secrets" NEW_PATH="/var/run/secrets"

# Check Tower credential types curl -k -u admin:password https://localhost/api/v2/credential_types/ | \ jq -r '.results[] | select(.injectors | contains("'$OLD_PATH'")) | {id, name, injectors}'

# Check Tower settings grep -r "$OLD_PATH" /etc/tower/ grep -r "$OLD_PATH" /var/lib/awx/

# Check environment variables in running processes ps aux | grep celery | awk '{print $2}' | while read pid; do sudo cat /proc/$pid/environ 2>/dev/null | tr '\0' '\n' | grep -E "SECRET|CREDENTIAL|VAULT" done

# Check Kubernetes configuration kubectl get pods -l app=tower -o yaml | grep -A10 "volumeMounts"

# Check for symlinks or mount points ls -la /etc/tower/secrets/ mount | grep secrets

# Find files with old path references sudo find /var/lib/awx -type f -exec grep -l "$OLD_PATH" {} \; 2>/dev/null ```

Step 2: Update Tower Credential Types

Modify credential type injectors to use new paths:

```bash # List credential types using old path curl -k -u admin:password https://localhost/api/v2/credential_types/ | \ jq -r '.results[] | select(.injectors | contains("'$OLD_PATH'")) | .id'

# For each credential type, update the injector # Example for Machine credential type curl -X PATCH -k -u admin:password \ -H "Content-Type: application/json" \ -d '{ "injectors": { "file": { "template": "[default]\nusername={{ username }}\npassword={{ password }}\n" }, "env": { "ANSIBLE_NET_SSH_KEYFILE": "{{ tower.filename }}", "ANSIBLE_NET_USERNAME": "{{ username }}", "ANSIBLE_NET_PASSWORD": "{{ password }}" }, "extra_vars": { "ansible_ssh_private_key_file": "{{ tower.filename }}" } } }' \ https://localhost/api/v2/credential_types/1/

# For custom credential types with file injectors curl -X PATCH -k -u admin:password \ -H "Content-Type: application/json" \ -d '{ "injectors": { "file": { "template": "{{ vault_token }}" }, "env": { "VAULT_TOKEN_PATH": "/var/run/secrets/vault_token" } } }' \ https://localhost/api/v2/credential_types/15/ ```

Step 3: Update Custom Credential Plugins

If using custom credential plugins:

```python # Find credential plugins find /usr/share/ansible/plugins -name "*.py" -exec grep -l "secrets" {} \;

# Edit plugin to use new path sudo vim /usr/share/ansible/plugins/connection/ssh.py

# Update path references # OLD: # key_file = '/etc/tower/secrets/ssh_key' # NEW: # key_file = os.environ.get('SSH_KEY_PATH', '/var/run/secrets/ssh_key')

# Or use environment variable for flexibility import os

def get_credential_path(filename): """Get credential file path from environment or default.""" base_path = os.environ.get('TOWER_SECRETS_PATH', '/var/run/secrets') return os.path.join(base_path, filename) ```

Step 4: Update Tower Environment Configuration

Set environment variables for the new path:

```bash # Edit Tower settings sudo vim /etc/tower/settings.py

# Add or update environment configuration import os

# Set base path for secrets TOWER_SECRETS_BASE_PATH = os.environ.get('TOWER_SECRETS_PATH', '/var/run/secrets')

# For Kubernetes deployments, this might be # TOWER_SECRETS_BASE_PATH = '/var/run/secrets/tower/'

# Update supervisor configuration for workers sudo vim /etc/supervisord.d/tower-worker.conf

[program:tower-worker] environment= TOWER_SECRETS_PATH="/var/run/secrets", VAULT_ADDR="https://vault.internal:8200", VAULT_TOKEN_PATH="/var/run/secrets/vault_token" # ... rest of config ```

Step 5: Clean Up Old Mount Points

Remove or fix stale mount points:

```bash # Check if old path is a mount point mountpoint /etc/tower/secrets && echo "Is mount point" || echo "Not mount point"

# Check if it's a symlink ls -la /etc/tower/secrets

# If symlink to non-existent target sudo rm /etc/tower/secrets

# If mount point, unmount it sudo umount /etc/tower/secrets

# Remove empty directory sudo rmdir /etc/tower/secrets 2>/dev/null || echo "Directory not empty or in use"

# Create symlink to new location (migration strategy) sudo ln -s /var/run/secrets /etc/tower/secrets

# Or create migration script for running processes cat > /usr/local/bin/migrate_secrets_path.sh << 'EOF' #!/bin/bash OLD_PATH="/etc/tower/secrets" NEW_PATH="/var/run/secrets"

# Copy any remaining files if [ -d "$OLD_PATH" ] && [ "$(ls -A $OLD_PATH 2>/dev/null)" ]; then echo "Copying files from $OLD_PATH to $NEW_PATH" cp -a "$OLD_PATH"/* "$NEW_PATH"/ fi

# Create symlink rm -rf "$OLD_PATH" ln -s "$NEW_PATH" "$OLD_PATH" echo "Migration complete" EOF

chmod +x /usr/local/bin/migrate_secrets_path.sh ```

Step 6: Restart Workers to Pick Up Changes

Force workers to reload configuration:

```bash # Stop workers gracefully sudo supervisorctl stop tower-worker:*

# Clear any cached environment sudo -u awx awx-manage clear_cache

# Start workers with new configuration sudo supervisorctl start tower-worker:*

# Or full Tower restart sudo ansible-tower-service restart

# Verify workers have new environment ps aux | grep "celery" | awk '{print $2}' | head -1 | xargs -I {} cat /proc/{}/environ | tr '\0' '\n' | grep SECRETS

# Should show: TOWER_SECRETS_PATH=/var/run/secrets ```

Step 7: Update Kubernetes Configuration

If running in Kubernetes:

yaml
# Update deployment to use new secret mount
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tower
spec:
  template:
    spec:
      containers:
      - name: tower
        volumeMounts:
        # OLD:
        # - name: secrets
        #   mountPath: /etc/tower/secrets
        # NEW:
        - name: secrets
          mountPath: /var/run/secrets
          readOnly: true
        env:
        - name: TOWER_SECRETS_PATH
          value: /var/run/secrets
      volumes:
      - name: secrets
        secret:
          secretName: tower-secrets

Apply the update:

```bash # Apply updated configuration kubectl apply -f tower-deployment.yaml

# Force rolling update to restart pods kubectl rollout restart deployment/tower

# Monitor rollout kubectl rollout status deployment/tower

# Verify new mount in pods kubectl exec -it deployment/tower -- ls -la /var/run/secrets/ kubectl exec -it deployment/tower -- cat /var/run/secrets/aws_access_key ```

Step 8: Test Credential Access

Verify credentials work from new location:

```bash # Check files exist in new location ls -la /var/run/secrets/

# Verify file contents cat /var/run/secrets/aws_access_key # Should show valid access key

# Test via Tower API curl -X POST -k -u admin:password \ -H "Content-Type: application/json" \ -d '{"credential': 42}' \ https://localhost/api/v2/job_templates/1/launch/ | jq '.job'

# Monitor job for credential errors curl -k -u admin:password https://localhost/api/v2/jobs/12345/ | jq '.status, .job_explanation' ```

Verification

Confirm credentials are being read from the new location:

```bash # Verify no references to old path grep -r "/etc/tower/secrets" /etc/tower/ /var/lib/awx/ 2>/dev/null || echo "No old path references found"

# Check credential injection in new job curl -X POST -k -u admin:password \ https://localhost/api/v2/job_templates/1/launch/ | jq -r '.job' > /tmp/job_id.txt

# Check job logs for successful credential use curl -k -u admin:password "https://localhost/api/v2/jobs/$(cat /tmp/job_id.txt)/job_events/?order_by=start_line" | \ jq -r '.results[] | select(.event == "runner_on_ok") | .event_data.res.msg'

# Verify worker environment sudo supervisorctl status | grep tower-worker ps aux | grep "celery.*tower" | awk '{print $2}' | head -1 | \ xargs sudo cat /proc/{}/environ | tr '\0' '\n' | grep TOWER_SECRETS_PATH

# Expected: TOWER_SECRETS_PATH=/var/run/secrets

# Run a playbook that uses credentials ansible-playbook test_credentials.yml -v # Should show successful authentication ```

  • [ansible-credential-injection-fails](/articles/ansible-credential-injection-fails)
  • [ansible-vault-integration-path-issues](/articles/ansible-vault-integration-path-issues)
  • [ansible-kubernetes-secret-mount-failures](/articles/ansible-kubernetes-secret-mount-failures)
  • [WordPress troubleshooting: Ansible Artifact Download Uses an Old Mi](ansible-artifact-download-uses-an-old-mirror-after-proxy-change)
  • [WordPress troubleshooting: Ansible Audit Trail Misses Events Under ](ansible-audit-trail-misses-events-under-burst-load)
  • [WordPress troubleshooting: Ansible Background Worker Gets Stuck in ](ansible-background-worker-stuck-in-a-retry-loop)
  • [WordPress troubleshooting: Ansible Backup Completes but Restore Fai](ansible-backup-completes-but-restore-fails-checksum-validation)
  • [WordPress troubleshooting: Ansible Batch Importer Duplicates Rows A](ansible-batch-importer-duplicates-rows-after-a-retry)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "WordPress troubleshooting: Ansible Runtime Keeps Reading an Old Cre", "description": "Learn how to fix Ansible Runtime Keeps Reading an Old Credential File After a Secret Mount Move. Professional WordPress troubleshooting solutions with step-by-step guidance. WP error fix, WordPress optimization, WP security, WordPress performance.", "url": "https://www.fixwikihub.com/ansible-runtime-keeps-reading-an-old-credential-file-after-secret-mount-move", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-02-12T11:18:00.731Z", "dateModified": "2026-02-12T11:18:00.731Z" } </script>