Add aws app and config for mfa.

Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
Mariano Uvalle 2025-07-14 18:16:56 -07:00
parent d72369c20b
commit c4b62ddeee
5 changed files with 169 additions and 3 deletions

View file

@ -4,8 +4,9 @@
settings = {
general = {
lock_cmd = "pidof hyprlock || hyprlock"; # Avoid starting hyprlock multiple times.
before_sleep_cmd = "loginctl lock-session"; # lock before suspend.
before_sleep_cmd = "pidof hyprlock || hyprlock --no-fade-in --immediate"; # lock before suspend.
after_sleep_cmd = "hyprctl dispatch dpms on"; # to avoid having to press a key twice to turn on the display.
inhibit_sleep = 3; # Wait for hyprlock.
};
listener = [
{

View file

@ -20,6 +20,8 @@
"private_keys/ace" = {
path = "/home/jmug/.ssh/id_ace";
};
"aws/mfa_serial" = {};
"aws/role_arn" = {};
};
};
}

View file

@ -62,6 +62,9 @@ in
# Dev tools
flyctl
pkgs-unstable.claude-code
# AWS tools
awscli2
(callPackage ../../nixos-modules/shell-apps/aws-cli-mfa.nix {})
];
pointerCursor = {
@ -155,8 +158,19 @@ in
fly = "flyctl";
# TODO: Interpolate the name of the host here.
nrsw = "sudo nixos-rebuild switch --flake /home/jmug/nixos#asahi"; # parametrize this as home dir.
awsmfa = "eval $(aws-cli-mfa)";
uawsmfa = "eval $(aws-cli-mfa --unset)";
};
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
home.activation.aws-cli-mfa-config = lib.hm.dag.entryAfter ["writeBoundary"] ''
mkdir -p ~/.config/aws-cli-mfa
cat > ~/.config/aws-cli-mfa/config.yaml << EOF
mfa_serial: $(cat ${config.sops.secrets."aws/mfa_serial".path})
role_arn: $(cat ${config.sops.secrets."aws/role_arn".path})
session_duration: 43200
EOF
'';
}

View file

@ -0,0 +1,146 @@
{ pkgs }:
pkgs.writeShellApplication {
name = "aws-cli-mfa";
runtimeInputs = with pkgs; [
awscli2
yubikey-manager
yq-go
jq
];
text = ''
set -euo pipefail
CONFIG_DIR="$HOME/.config/aws-cli-mfa"
CONFIG_FILE="$CONFIG_DIR/config.yaml"
usage() {
echo "Usage: $0 [--unset] [--help]"
echo ""
echo "Options:"
echo " --unset Unset AWS environment variables"
echo " --help Show this help message"
echo ""
echo "This tool assumes temporary credentials with MFA using a YubiKey."
echo "Configuration file: $CONFIG_FILE"
}
unset_vars() {
if [[ -t 1 ]]; then
# Running directly - can't unset in parent shell, show instructions
echo "To unset AWS environment variables, run:"
echo " eval \$(aws-cli-mfa --unset)"
else
# Running from eval - output unset commands
echo "AWS environment variables unset." >&2
cat << EOF
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_SECURITY_TOKEN
EOF
fi
}
# Parse command line arguments
if [[ $# -gt 0 ]]; then
case "$1" in
--unset)
unset_vars
exit 0
;;
--help)
usage
exit 1
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
fi
# Check if config file exists
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Error: Configuration file not found: $CONFIG_FILE"
echo ""
echo "Please create a configuration file with the following format:"
echo ""
echo "mfa_serial: arn:aws:iam::123456789012:mfa/username"
echo "role_arn: arn:aws:iam::123456789012:role/RoleName"
echo "session_duration: 43200 # optional, defaults to 43200 seconds (12 hours)"
echo ""
exit 1
fi
# Read configuration
MFA_SERIAL=$(yq eval '.mfa_serial' "$CONFIG_FILE")
ROLE_ARN=$(yq eval '.role_arn' "$CONFIG_FILE")
SESSION_DURATION=$(yq eval '.session_duration // 43200' "$CONFIG_FILE")
if [[ "$MFA_SERIAL" == "null" || "$ROLE_ARN" == "null" ]]; then
echo "Error: mfa_serial and role_arn must be specified in $CONFIG_FILE"
exit 1
fi
echo "Getting TOTP code from YubiKey for device: $MFA_SERIAL" >&2
# Get TOTP code from YubiKey
if ! TOTP_CODE=$(ykman oath accounts code "$MFA_SERIAL" -s); then
echo "Error: Failed to get TOTP code from YubiKey for device '$MFA_SERIAL'"
echo "Available OATH accounts:"
ykman oath accounts list || echo "No OATH accounts found on YubiKey"
exit 1
fi
echo "Assuming role with MFA..." >&2
# Call AWS STS assume-role
if ! STS_RESPONSE=$(aws sts assume-role \
--role-arn "$ROLE_ARN" \
--role-session-name "aws-cli-mfa-$(date +%s)" \
--serial-number "$MFA_SERIAL" \
--token-code "$TOTP_CODE" \
--duration-seconds "$SESSION_DURATION" \
--output json); then
echo "Error: Failed to assume role. Check your AWS credentials and configuration."
exit 1
fi
# Extract credentials from response
AWS_ACCESS_KEY_ID=$(echo "$STS_RESPONSE" | jq -r '.Credentials.AccessKeyId')
AWS_SECRET_ACCESS_KEY=$(echo "$STS_RESPONSE" | jq -r '.Credentials.SecretAccessKey')
AWS_SESSION_TOKEN=$(echo "$STS_RESPONSE" | jq -r '.Credentials.SessionToken')
EXPIRATION=$(echo "$STS_RESPONSE" | jq -r '.Credentials.Expiration')
if [[ "$AWS_ACCESS_KEY_ID" == "null" || "$AWS_SECRET_ACCESS_KEY" == "null" || "$AWS_SESSION_TOKEN" == "null" ]]; then
echo "Error: Failed to extract credentials from AWS response"
exit 1
fi
# Check if running from eval (stdout is a pipe) or directly
if [[ -t 1 ]]; then
# Running directly - can't export to parent shell, show instructions
echo "Success! AWS temporary credentials retrieved."
echo "Credentials expire at: $EXPIRATION"
echo ""
echo "To use these credentials in your current shell, run:"
echo " eval \$(aws-cli-mfa)"
echo ""
echo "To unset the credentials later, run:"
echo " eval \$(aws-cli-mfa --unset)"
else
# Running from eval - only output export commands
echo "Success! AWS temporary credentials have been set." >&2
echo "Credentials expire at: $EXPIRATION" >&2
echo "" >&2
echo "To unset the credentials later, run:" >&2
echo " eval \$(aws-cli-mfa --unset)" >&2
# Output export commands for eval
cat << EOF
export AWS_ACCESS_KEY_ID='$AWS_ACCESS_KEY_ID'
export AWS_SECRET_ACCESS_KEY='$AWS_SECRET_ACCESS_KEY'
export AWS_SESSION_TOKEN='$AWS_SESSION_TOKEN'
export AWS_SECURITY_TOKEN='$AWS_SESSION_TOKEN'
EOF
fi
'';
}

View file

@ -2,6 +2,9 @@ private_keys:
jmug: ENC[AES256_GCM,data:OOokznYtow9Ra0FRwBydclS6Z7w7mV9RTtHpjyw8NT+rV2VzTm/K8qYSw9N3gDbao5JOZH+LlVIL0UX2ZqGk8j/4D67LP+FmTUiIqyHH3LI8RcXAixIbR+zwV19Gr7DiFWw8jCyXQFFBJJRIatZLA5T2r23pYxL5drpQUcWsttuHRN7RwfXq2eFQogufzgfUqQgMkqvx64prDbrNq7bqZpPZE/vrCIoSK4rgTZCokD2hoIdEdzxLaVlbaoMMY7Dg83ojFU2Fpe5TWuS+duxUYjCPh86uoa3d0dsKoVkB9zd56EO5z0SB94tht9WJzmqKPh2gZ6DvV+wdaMH7wSqnUB6c2mE3i886Y4LQJjUFzpu1dnZaEEvJ0HI5jelZ3QVwjhp8/Zrkl/MwOG+lCHcIu2T7nQ/O0NxBNgMYbEYHGDcqi+anAzw2eNfep//dC+lT0qiXYAlzdDIIcTE7sL40qq7xXWtZJR4I6B+EOcYVn+zu93tsLiNke26h9ZoiAk40oxNZaiTPKnLkSvPtVZcoA53PamVKTS+tkXd+Pzt07lUgumTRIybkjE7i0hrhBrAv98NqvHobWfJVsQaH,iv:CDOXxoNTTw0rTsQbyYcXk0xL1UZtSbC0EG0audUwriM=,tag:XN2sujg9YHc5FtwCGPweSQ==,type:str]
matcha: ENC[AES256_GCM,data:ySty7UiMQirmXmTf0unJuWX731NKKHH0lJQUxl4LlOHixm+1I13SNF7g+G9XYQzkvX/ijhzkDGLwIERVq1Di1UJsEw5w2YxeSitrzoJT0NZx+/Ip8DdgD/k+KnWgm7NeswcUTlt5cQbw7/IPkmRZ3qOV9UueMhH09AYEaSLpiKBUKQuMP/u2YgklwgWvVxW6syMPiYWmSnEfP3MfnkUbhhCuexdb2a/26BfVBbZeuLgBi25dxvRlykR6/uijCk3lHl84xPgWGKyR/130aHC2wp3arVjWO8/p2h2dobtnEnnDCWz2t+3CD1Qc3kdJcSNXhlFJWESOxzJp2KZFIKc3tF4emRSJ+EwP9byavrRozkHCBx4qCvLfdBzndJalR6ebh3jMyQIdRNBPG4x7+nSNnWZeK2uXhHg+m18CJfq2nKgVrpB4x+i48LyEwv907hFGLhfB4o+gfHxQtJYNoP3F0HX2xYndIjk6ihzbUi4C4YG7eUOF1IQ174FshFiadLfqAS+yh8oDUpKVthRu8kda6pPCtMI2jugIYzFrA30k6RWSj6QIsV8FE/jUdw++Htw9dutJSVsAncqCy4mZvqTIh0Cubnp9seBHX0l7vX0kAznoUMTK07lHBbkyeVlZHvKA+y34+SJVLpuS1eVjNhkeNpKkWAxwkoc/84ozpM2078bzy48rvMJBYIzjgqnqREnSs35Q8bQcHB32R0T6JEPoT7S8Z1GS7QB7kv8ulbA2H11Z,iv:8EBPvh7dpv23NtgwUmLn+2m/CKI6dZq72AXvB1OOdlc=,tag:1RCXZDcLOUP+hznVRgzMuA==,type:str]
ace: ENC[AES256_GCM,data:rqhEzNlR978ZS+OppyHOWjYTfRKifRMXpqQfKS48oHkbcq7fQF6QtBBz3Ad4yFE3UJECcPipWqi5dhAMDVmOZT9gxEvEdhf6T7ecPHuQo95eUGPC46ZjoEwv7bykNBcncZFd4EHU2szuyqWb+xpGwJtnKqxDzi0ygsS6z/8ySlJWJ+LqCWrwsnERIblW5vgCvrT5Q2zkz3mR8pDNFc4Kh7B27SCBFG3EHST/NsEMhW0hU2eoIK5ppvHaKhVTx22ov2dP+nNbAhMi1WqHCGyQC2X4jeGGPXV8qXvev3Dl0s/VTCUsyqIIU5hPggWo++/XEspaoiLX7BsGklcfL5qGbF6YmSHYDBdgs5VOTzRUsepegmF79qXKO2XRgksH0he3aUOkpv9TVsmBIyPWqClOtA/QiQQ12gLCvqmBLcj66prvRS9qsZ++wxIINBFWhZh8F24pUHf6qpLpXmiEz9l7n1WocJjVgsI0hebYNN7sLtX/2IxVhPjNHVJQPPkDdLuw2841QKdO8cyDjCho3gbm1+RRDeyZWFSnnQUnKA+p2rE9ahtIJtUTuri7egGroJxPRWwgx2r3FW1Cm1ScdcL2W2AJQtnlJNqRnK/jXHXU3QHBkSiLTSwDLXG/0FV23/4Q+wCYp7ivBbztywT2Ngs8IMrFXz/6a7oGPaRqeendaVLJwrpkjruWv/nTdw0/PRchkLFuuP9ncJYVYaI2X6WwE9eiCAyG99M1X587w7SQe7cx,iv:HHfrC8PMHQS96YAzsyu7u52josTWNpgGa+qdjTKk7mk=,tag:9njC2670XZBUusf3cIv+gg==,type:str]
aws:
role_arn: ENC[AES256_GCM,data:YlYtqpsiTgHayuCFxY3pKfh5aBjNPf0UMGCoR+mFBUxe1CIU/Nkm+gzAOzwI,iv:Oo8d5y2g3lIVhrQgBT80PSxnZC0qXdqrumx76V1dz6w=,tag:gGJLjCYgcR3nHGhEbEpIGw==,type:str]
mfa_serial: ENC[AES256_GCM,data:O1Tzop8mjG48Hw19tAVoTvQ/z4UR3MvXzyEZZ8RArp73E7GsrR6jtsXnbrtNrigTgA==,iv:HumTqCU69e7zAUMsTn4lKr1WY8hU/SS/p5N2IXPTVWY=,tag:ATfjwZGSDmCaV7Zuoe9XzA==,type:str]
yubico:
u2f_keys:
jmug: ENC[AES256_GCM,data:Z42zNo1DaQutPIfE+0PEAK5F1fmspJp6jmosHSHsUN6dSG4zY93Tdmvisxg0hFUbuMlYg/06z3bsagFY4q+9Eg6qCLqzj1Uzs3VA0vEP+N0UlR5YZvneWzhnw2KYaPSJ/dsxt9tSfJO89P5ffeJgfSds2hLRWngm0agkmZ1P9lRbY0iMTUGl9se4V/anydwH69GQLyul5EtXHr9KZyU2pkT86zQSHGqiiMm85TfyixTWi/PWFl1jtDlyUbvN2HZYFGdQ6O0E,iv:TYel/hCVAMQL1rqok/1YMqcGFuXmsvkwUcA988VULW8=,tag:dnPQiY5i3oHbsC9zdXvY4w==,type:str]
@ -44,7 +47,7 @@ sops:
UjlDQ0Y5QnY4dmlVVFZrM0IyZzlISWcKwpQY9/f1O2v78/9/dCZ7HPE3wVwQ4COG
a0E+oMEgBIeQny9LyfhUW2V/HKhYhFNPJaZrNM4J1zL+bz2ucdErmw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-06-19T19:52:49Z"
mac: ENC[AES256_GCM,data:Fz/3QEoAjJy4psanCwDvIjUFSVDaSK+/Qjyr2M2c7eetv+USDaP1CraXaTK/OwKQfDWdnPHUOqfW1Oj51XJSPPoRlyYjXJxODjVXZfHo/EwnbpJs/81Lx66lYKljgCopFE6y2a7cFkM25g9aehyPhP/zdwULa/EmXcnuIimH8A0=,iv:dniso33D2uf4YUUbbODsbfm2k6dZdWdBTBPvnXTHL34=,tag:89KOctxIJ+lT7xUQR0qetA==,type:str]
lastmodified: "2025-07-15T00:54:12Z"
mac: ENC[AES256_GCM,data:KYcoP9vtmLRDaYVUfGtRy5XfHVgwuF6DxV2U2ZCQZ0oUTJRXj9eSjWJFRXlT243GQ11SgoUAcGFhATAOyqla3jsXuDiTn5oMydEwgSjVxjFpQf2RujCCqekk/7rcrRC7hVlUryNFylYy9wA9TWkGNGpaB/Z/a+Oaa5naDXODRAE=,iv:a6YVHIp4dIGxCIDmwM8r2AlBO40gptiLeNoyhRdJ+m8=,tag:DKsGjalA69F1ups/2sO5og==,type:str]
unencrypted_suffix: _unencrypted
version: 3.10.2