Skip to content

Commit cd08bcf

Browse files
authored
Merge pull request #373 from rundeck-plugins/RUN-2566
RUN-2566: add a new mechanisms to wait from the prompt of vault-ids
2 parents 38636b5 + d027e48 commit cd08bcf

File tree

8 files changed

+217
-38
lines changed

8 files changed

+217
-38
lines changed

functional-test/src/test/groovy/functional/BasicIntegrationSpec.groovy

+29
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,33 @@ class BasicIntegrationSpec extends BaseTestConfiguration {
258258
logs.findAll {it.log.contains("\"token\": 13231232312321321321321")}.size() == 1
259259
}
260260

261+
def "test use encrypted user file with password authentication"(){
262+
when:
263+
264+
def jobId = "0ea27de5-ef36-4a2f-b09c-1bd548eb78d4"
265+
266+
JobRun request = new JobRun()
267+
request.loglevel = 'DEBUG'
268+
269+
def result = client.apiCall {api-> api.runJob(jobId, request)}
270+
def executionId = result.id
271+
272+
def executionState = waitForJob(executionId)
273+
274+
def logs = getLogs(executionId)
275+
Map<String, Integer> ansibleNodeExecutionStatus = TestUtil.getAnsibleNodeResult(logs)
276+
277+
then:
278+
executionState!=null
279+
executionState.getExecutionState()=="SUCCEEDED"
280+
ansibleNodeExecutionStatus.get("ok")!=0
281+
ansibleNodeExecutionStatus.get("unreachable")==0
282+
ansibleNodeExecutionStatus.get("failed")==0
283+
ansibleNodeExecutionStatus.get("skipped")==0
284+
ansibleNodeExecutionStatus.get("ignored")==0
285+
logs.findAll {it.log.contains("encryptVariable ansible_ssh_password:")}.size() == 1
286+
logs.findAll {it.log.contains("\"environmentTest\": \"test\"")}.size() == 1
287+
logs.findAll {it.log.contains("\"token\": 13231232312321321321321")}.size() == 1
288+
}
289+
261290
}

functional-test/src/test/resources/docker/rundeck/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RUN apt-get -y install sshpass && \
2222
apt-get -y install sudo && \
2323
pip3 install --upgrade pip
2424

25-
RUN pip3 install ansible
25+
RUN pip3 install ansible==9.6.0
2626

2727
RUN ln -s /usr/bin/python3 /usr/bin/python
2828

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<joblist>
2+
<job>
3+
<defaultTab>nodes</defaultTab>
4+
<description></description>
5+
<dispatch>
6+
<excludePrecedence>true</excludePrecedence>
7+
<keepgoing>false</keepgoing>
8+
<rankOrder>ascending</rankOrder>
9+
<successOnEmptyNodeFilter>false</successOnEmptyNodeFilter>
10+
<threadcount>1</threadcount>
11+
</dispatch>
12+
<executionEnabled>true</executionEnabled>
13+
<group>Ansible</group>
14+
<id>0ea27de5-ef36-4a2f-b09c-1bd548eb78d4</id>
15+
<loglevel>INFO</loglevel>
16+
<name>simple-inline-playbook-user-encryption-and-ssh-pass</name>
17+
<nodeFilterEditable>false</nodeFilterEditable>
18+
<nodefilters>
19+
<filter>name: ssh-node </filter>
20+
</nodefilters>
21+
<nodesSelectedByDefault>true</nodesSelectedByDefault>
22+
<plugins />
23+
<scheduleEnabled>true</scheduleEnabled>
24+
<sequence keepgoing='false' strategy='node-first'>
25+
<command>
26+
<node-step-plugin type='com.batix.rundeck.plugins.AnsiblePlaybookInlineWorkflowNodeStep'>
27+
<configuration>
28+
<entry key='ansible-become' value='false' />
29+
<entry key='ansible-encrypt-extra-vars' value='false' />
30+
<entry key='ansible-extra-param' value='--extra-vars=@/home/rundeck/ansible/user-encrypted-env-vars.yaml' />
31+
<entry key='ansible-playbook-inline' value='- hosts: all&#10; gather_facts: false&#10; tasks:&#10;&#10; - name: Hello World!&#10; debug:&#10; msg: "Hello World!"&#10; - name: wait&#10; shell: "sleep 15"&#10; register: sh_output&#10; &#10; - name: Get Disk Space&#10; shell: "df -h"&#10; register: sh_output&#10;&#10;&#10; &#10; - debug: msg={{hostvars[inventory_hostname]}}&#10; - debug: var=sh_output.stdout_lines&#10; - debug: msg="{{ username }}"&#10; - debug: msg="{{ token }}"&#10; - debug: msg="{{ environmentTest }}"&#10;' />
32+
<entry key='ansible-ssh-auth-type' value='password' />
33+
<entry key='ansible-ssh-passphrase-option' value='option.password' />
34+
<entry key='ansible-ssh-password-storage-path' value='keys/project/ansible-test/ssh-node.pass' />
35+
<entry key='ansible-ssh-use-agent' value='false' />
36+
<entry key='ansible-ssh-user' value='rundeck' />
37+
<entry key='ansible-vault-storage-path' value='keys/project/ansible-test/vault-user.pass' />
38+
</configuration>
39+
</node-step-plugin>
40+
</command>
41+
</sequence>
42+
<uuid>0ea27de5-ef36-4a2f-b09c-1bd548eb78d4</uuid>
43+
</job>
44+
</joblist>

src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleRunner.java

+52-26
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ public static AnsibleRunner buildAnsibleRunner(AnsibleRunnerContextBuilder conte
290290
File tempVaultFile ;
291291
File tempSshVarsFile ;
292292
File tempBecameVarsFile ;
293+
File vaultPromptFile;
293294

294295
public void deleteTempDirectory(Path tempDirectory) throws IOException {
295296
Files.walkFileTree(tempDirectory, new SimpleFileVisitor<Path>() {
@@ -337,7 +338,6 @@ public int run() throws Exception {
337338

338339
if(ansibleVault==null){
339340
tempInternalVaultFile = AnsibleVault.createVaultScriptAuth("ansible-script-vault");
340-
341341
ansibleVault = AnsibleVault.builder()
342342
.baseDirectory(baseDirectory)
343343
.masterPassword(AnsibleUtil.randomString())
@@ -387,12 +387,7 @@ public int run() throws Exception {
387387

388388
useAnsibleVault = ansibleVault.checkAnsibleVault();
389389

390-
if(useAnsibleVault) {
391-
tempInternalVaultFile = ansibleVault.getVaultPasswordScriptFile();
392-
393-
procArgs.add("--vault-id");
394-
procArgs.add("internal-encrypt@" + tempInternalVaultFile.getAbsolutePath());
395-
}else{
390+
if(!useAnsibleVault) {
396391
System.err.println("WARN: ansible-vault is not installed, extra-vars will not be encrypted.");
397392
}
398393
}
@@ -431,12 +426,6 @@ public int run() throws Exception {
431426
procArgs.add("--extra-vars" + "=" + "@" + tempVarsFile.getAbsolutePath());
432427
}
433428

434-
if (vaultPass != null && !vaultPass.isEmpty()) {
435-
tempVaultFile = ansibleVault.getVaultPasswordScriptFile();
436-
procArgs.add("--vault-id");
437-
procArgs.add(tempVaultFile.getAbsolutePath());
438-
}
439-
440429
if (sshPrivateKey != null && !sshPrivateKey.isEmpty()) {
441430
String privateKeyData = sshPrivateKey.replaceAll("\r\n", "\n");
442431
tempPkFile = AnsibleUtil.createTemporaryFile("id_rsa", privateKeyData);
@@ -506,16 +495,14 @@ public int run() throws Exception {
506495
procArgs.addAll(tokenizeCommand(extraParams));
507496
}
508497

509-
if (debug) {
510-
System.out.println(" procArgs: " + procArgs);
511-
}
512-
513498
if(processExecutorBuilder==null){
514499
processExecutorBuilder = ProcessExecutor.builder();
515500
}
516501

517-
//set main process command
518-
processExecutorBuilder.procArgs(procArgs);
502+
if (debug) {
503+
System.out.println(" procArgs: " + procArgs);
504+
processExecutorBuilder.debug(true);
505+
}
519506

520507
if (baseDirectory != null) {
521508
processExecutorBuilder.baseDirectory(baseDirectory.toFile());
@@ -540,20 +527,51 @@ public int run() throws Exception {
540527
processEnvironment.put("SSH_AUTH_SOCK", this.sshAgent.getSocketPath());
541528
}
542529

543-
processExecutorBuilder.environmentVariables(processEnvironment);
544-
545530
//set STDIN variables
546-
List<String> stdinVariables = new ArrayList<>();
531+
List<VaultPrompt> stdinVariables = new ArrayList<>();
532+
533+
if(useAnsibleVault || vaultPass != null ){
534+
vaultPromptFile = File.createTempFile("vault-prompt", ".log");
535+
}
547536

548537
if (useAnsibleVault) {
549-
stdinVariables.add(ansibleVault.getMasterPassword() + "\n");
538+
VaultPrompt vaultPrompt = VaultPrompt.builder()
539+
.vaultId("internal-encrypt")
540+
.vaultPassword(ansibleVault.getMasterPassword() + "\n")
541+
.build();
542+
543+
stdinVariables.add(vaultPrompt);
544+
processEnvironment.put("LOG_PATH", vaultPromptFile.getAbsolutePath());
545+
546+
tempInternalVaultFile = ansibleVault.getVaultPasswordScriptFile();
547+
548+
procArgs.add("--vault-id");
549+
procArgs.add("internal-encrypt@" + tempInternalVaultFile.getAbsolutePath());
550550
}
551551

552552
if (vaultPass != null && !vaultPass.isEmpty()) {
553-
stdinVariables.add(vaultPass + "\n");
553+
VaultPrompt vaultPrompt = VaultPrompt.builder()
554+
.vaultId("None")
555+
.vaultPassword(vaultPass + "\n")
556+
.build();
557+
558+
stdinVariables.add(vaultPrompt);
559+
processEnvironment.putIfAbsent("LOG_PATH", vaultPromptFile.getAbsolutePath());
560+
561+
tempVaultFile = ansibleVault.getVaultPasswordScriptFile();
562+
procArgs.add("--vault-id");
563+
procArgs.add(tempVaultFile.getAbsolutePath());
554564
}
555565

566+
//set main process command
567+
processExecutorBuilder.procArgs(procArgs);
556568
processExecutorBuilder.stdinVariables(stdinVariables);
569+
processExecutorBuilder.environmentVariables(processEnvironment);
570+
571+
//set vault prompt file
572+
if(vaultPromptFile !=null){
573+
processExecutorBuilder.promptStdinLogFile(vaultPromptFile);
574+
}
557575

558576
proc = processExecutorBuilder.build().run();
559577

@@ -627,6 +645,10 @@ public int run() throws Exception {
627645
tempInternalVaultFile.deleteOnExit();
628646
}
629647

648+
if(vaultPromptFile != null && !vaultPromptFile.delete()){
649+
vaultPromptFile.deleteOnExit();
650+
}
651+
630652
if (usingTempDirectory && !retainTempDirectory) {
631653
deleteTempDirectory(baseDirectory);
632654
}
@@ -668,9 +690,13 @@ public boolean registerKeySshAgent(String keyPath) throws Exception {
668690
env.put("SSH_ASKPASS", tempPassVarsFile.getAbsolutePath());
669691
}
670692

671-
List<String> stdinVariables = new ArrayList<>();
693+
List<VaultPrompt> stdinVariables = new ArrayList<>();
672694
if (sshPassphrase != null && !sshPassphrase.isEmpty()) {
673-
stdinVariables.add(sshPassphrase + "\n");
695+
VaultPrompt sshPassPrompt = VaultPrompt.builder()
696+
.vaultPassword(sshPassphrase + "\n")
697+
.build();
698+
699+
stdinVariables.add(sshPassPrompt);
674700
}
675701

676702
ProcessExecutor processExecutor = ProcessExecutor.builder()

src/main/groovy/com/rundeck/plugins/ansible/ansible/AnsibleVault.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.rundeck.plugins.ansible.util.AnsibleUtil;
44
import com.rundeck.plugins.ansible.util.ProcessExecutor;
5+
import com.rundeck.plugins.ansible.util.VaultPrompt;
56
import lombok.Builder;
67
import lombok.Data;
78

@@ -72,8 +73,11 @@ public String encryptVariable(String key,
7273
}
7374

7475
//send values to STDIN in order
75-
List<String> stdinVariables = new ArrayList<>();
76-
stdinVariables.add(content);
76+
List<VaultPrompt> stdinVariables = new ArrayList<>();
77+
VaultPrompt vaultPrompt = VaultPrompt.builder()
78+
.vaultPassword(content)
79+
.build();
80+
stdinVariables.add(vaultPrompt);
7781

7882
Map<String, String> env = new HashMap<>();
7983
env.put("VAULT_ID_SECRET", masterPassword);

src/main/groovy/com/rundeck/plugins/ansible/util/ProcessExecutor.java

+52-9
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,8 @@
22

33
import lombok.Builder;
44

5-
import java.io.IOException;
6-
import java.io.OutputStream;
7-
import java.io.OutputStreamWriter;
5+
import java.io.*;
86
import java.util.List;
9-
import java.io.File;
107
import java.util.Map;
118

129
@Builder
@@ -18,12 +15,17 @@ public class ProcessExecutor {
1815

1916
private Map<String, String> environmentVariables;
2017

21-
private List<String> stdinVariables;
18+
private List<VaultPrompt> stdinVariables;
2219

2320
private boolean redirectErrorStream;
2421

22+
private File promptStdinLogFile;
23+
24+
private boolean debug;
25+
2526

2627
public Process run() throws IOException {
28+
2729
ProcessBuilder processBuilder = new ProcessBuilder()
2830
.command(procArgs)
2931
.redirectErrorStream(redirectErrorStream);
@@ -32,6 +34,7 @@ public Process run() throws IOException {
3234
processBuilder.directory(baseDirectory);
3335
}
3436

37+
3538
if(environmentVariables!=null){
3639
Map<String, String> processEnvironment = processBuilder.environment();
3740

@@ -47,19 +50,59 @@ public Process run() throws IOException {
4750

4851
if (stdinVariables != null) {
4952
try {
50-
51-
for (String stdinVariable : stdinVariables) {
52-
stdinw.write(stdinVariable);
53+
for (VaultPrompt stdinVariable : stdinVariables) {
54+
processPrompt(stdinw, stdinVariable);
5355
}
54-
stdinw.flush();
5556
} catch (Exception e) {
5657
System.err.println("error encryptFileAnsibleVault file " + e.getMessage());
5758
}
5859
}
60+
5961
stdinw.close();
6062
stdin.close();
6163

6264
return proc;
6365
}
6466

67+
private void processPrompt(OutputStreamWriter stdinw, final VaultPrompt vaultPrompt) throws Exception {
68+
if(promptStdinLogFile!=null){
69+
Thread stdinThread = new Thread(() -> {
70+
try {
71+
stdinw.write(vaultPrompt.getVaultPassword());
72+
stdinw.flush();
73+
} catch (IOException e) {
74+
throw new RuntimeException(e);
75+
}
76+
}
77+
);
78+
79+
//wait for prompt
80+
boolean promptFound = false;
81+
long start = System.currentTimeMillis();
82+
long end = start + 60 * 1000;
83+
BufferedReader reader = new BufferedReader(new FileReader(promptStdinLogFile));
84+
85+
while (!promptFound && System.currentTimeMillis() < end){
86+
String currentLine = reader.readLine();
87+
if(debug){
88+
System.out.println("waiting for vault password prompt ("+vaultPrompt.getVaultId()+")...");
89+
}
90+
if(currentLine!=null && currentLine.contains("Enter Password ("+vaultPrompt.getVaultId()+"):")){
91+
if(debug) {
92+
System.out.println(currentLine);
93+
}
94+
promptFound = true;
95+
//send password / content
96+
stdinThread.start();
97+
}
98+
Thread.sleep(2000);
99+
}
100+
reader.close();
101+
102+
}else{
103+
stdinw.write(vaultPrompt.getVaultPassword());
104+
stdinw.flush();
105+
}
106+
}
107+
65108
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.rundeck.plugins.ansible.util;
2+
3+
import lombok.Builder;
4+
import lombok.Data;
5+
6+
@Builder
7+
@Data
8+
public class VaultPrompt {
9+
10+
private String vaultId;
11+
private String vaultPassword;
12+
13+
}

0 commit comments

Comments
 (0)