diff --git a/mock_ssh_test.go b/mock_ssh_test.go index 6f11142..e0b6e04 100644 --- a/mock_ssh_test.go +++ b/mock_ssh_test.go @@ -1318,9 +1318,9 @@ func moduleAuthorizedKeyWithClient(_ *Executor, client sshRunner, args map[strin authKeysPath := joinPath(home, ".ssh", "authorized_keys") if state == "absent" { - // Remove key - escapedKey := replaceAll(key, "/", "\\/") - cmd := sprintf("sed -i '/%s/d' %q 2>/dev/null || true", escapedKey[:40], authKeysPath) + // Remove the exact key line when present. + cmd := sprintf("if [ -f %q ]; then sed -i '\\|^%s$|d' %q; fi", + authKeysPath, sedExactLinePattern(key), authKeysPath) _, _, _, _ = client.Run(context.Background(), cmd) return &TaskResult{Changed: true}, nil } @@ -1331,7 +1331,7 @@ func moduleAuthorizedKeyWithClient(_ *Executor, client sshRunner, args map[strin // Add key if not present cmd := sprintf("grep -qF %q %q 2>/dev/null || echo %q >> %q", - key[:40], authKeysPath, key, authKeysPath) + key, authKeysPath, key, authKeysPath) stdout, stderr, rc, err := client.Run(context.Background(), cmd) if err != nil || rc != 0 { return &TaskResult{Failed: true, Msg: stderr, Stdout: stdout, RC: rc}, nil diff --git a/modules.go b/modules.go index aef3b81..848974d 100644 --- a/modules.go +++ b/modules.go @@ -2585,9 +2585,9 @@ func (e *Executor) moduleAuthorizedKey(ctx context.Context, client sshExecutorCl authKeysPath := joinPath(home, ".ssh", "authorized_keys") if state == "absent" { - // Remove key - escapedKey := replaceAll(key, "/", "\\/") - cmd := sprintf("sed -i '/%s/d' %q 2>/dev/null || true", escapedKey[:40], authKeysPath) + // Remove the exact key line when present. + cmd := sprintf("if [ -f %q ]; then sed -i '\\|^%s$|d' %q; fi", + authKeysPath, sedExactLinePattern(key), authKeysPath) _, _, _, _ = client.Run(ctx, cmd) return &TaskResult{Changed: true}, nil } @@ -2596,9 +2596,9 @@ func (e *Executor) moduleAuthorizedKey(ctx context.Context, client sshExecutorCl _, _, _, _ = client.Run(ctx, sprintf("mkdir -p %q && chmod 700 %q && chown %s:%s %q", pathDir(authKeysPath), pathDir(authKeysPath), user, user, pathDir(authKeysPath))) - // Add key if not present + // Add the key if it is not already present. cmd := sprintf("grep -qF %q %q 2>/dev/null || echo %q >> %q", - key[:40], authKeysPath, key, authKeysPath) + key, authKeysPath, key, authKeysPath) stdout, stderr, rc, err := client.Run(ctx, cmd) if err != nil || rc != 0 { return &TaskResult{Failed: true, Msg: stderr, Stdout: stdout, RC: rc}, nil @@ -2611,6 +2611,11 @@ func (e *Executor) moduleAuthorizedKey(ctx context.Context, client sshExecutorCl return &TaskResult{Changed: true}, nil } +func sedExactLinePattern(value string) string { + pattern := regexp.QuoteMeta(value) + return replaceAll(pattern, "|", "\\|") +} + func (e *Executor) moduleDockerCompose(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) { projectSrc := getStringArg(args, "project_src", "") state := getStringArg(args, "state", "present") diff --git a/modules_adv_test.go b/modules_adv_test.go index bf33f7f..2b5170a 100644 --- a/modules_adv_test.go +++ b/modules_adv_test.go @@ -354,6 +354,25 @@ func TestModulesAdv_ModuleAuthorizedKey_Good_AddKey(t *testing.T) { assert.True(t, mock.containsSubstring("authorized_keys")) } +func TestModulesAdv_ModuleAuthorizedKey_Good_ShortKeyDoesNotPanic(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + testKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA short@host" + mock.expectCommand(`getent passwd deploy`, "/home/deploy", "", 0) + mock.expectCommand(`mkdir -p`, "", "", 0) + mock.expectCommand(`grep -qF.*echo`, "", "", 0) + mock.expectCommand(`chmod 600`, "", "", 0) + + result, err := moduleAuthorizedKeyWithClient(e, mock, map[string]any{ + "user": "deploy", + "key": testKey, + }) + + require.NoError(t, err) + assert.True(t, result.Changed) + assert.False(t, result.Failed) + assert.True(t, mock.hasExecuted(`grep -qF`)) +} + func TestModulesAdv_ModuleAuthorizedKey_Good_RemoveKey(t *testing.T) { e, mock := newTestExecutorWithMock("host1") testKey := "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDcT... user@host"