Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUN-2566: add a new mechanisms to wait from the prompt of vault-ids #373

Merged
merged 6 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,33 @@ class BasicIntegrationSpec extends BaseTestConfiguration {
logs.findAll {it.log.contains("\"token\": 13231232312321321321321")}.size() == 1
}

def "test use encrypted user file with password authentication"(){
when:

def jobId = "0ea27de5-ef36-4a2f-b09c-1bd548eb78d4"

JobRun request = new JobRun()
request.loglevel = 'DEBUG'

def result = client.apiCall {api-> api.runJob(jobId, request)}
def executionId = result.id

def executionState = waitForJob(executionId)

def logs = getLogs(executionId)
Map<String, Integer> ansibleNodeExecutionStatus = TestUtil.getAnsibleNodeResult(logs)

then:
executionState!=null
executionState.getExecutionState()=="SUCCEEDED"
ansibleNodeExecutionStatus.get("ok")!=0
ansibleNodeExecutionStatus.get("unreachable")==0
ansibleNodeExecutionStatus.get("failed")==0
ansibleNodeExecutionStatus.get("skipped")==0
ansibleNodeExecutionStatus.get("ignored")==0
logs.findAll {it.log.contains("encryptVariable ansible_ssh_password:")}.size() == 1
logs.findAll {it.log.contains("\"environmentTest\": \"test\"")}.size() == 1
logs.findAll {it.log.contains("\"token\": 13231232312321321321321")}.size() == 1
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ RUN apt-get -y install sshpass && \
apt-get -y install sudo && \
pip3 install --upgrade pip

RUN pip3 install ansible
RUN pip3 install ansible==9.6.0

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<joblist>
<job>
<defaultTab>nodes</defaultTab>
<description></description>
<dispatch>
<excludePrecedence>true</excludePrecedence>
<keepgoing>false</keepgoing>
<rankOrder>ascending</rankOrder>
<successOnEmptyNodeFilter>false</successOnEmptyNodeFilter>
<threadcount>1</threadcount>
</dispatch>
<executionEnabled>true</executionEnabled>
<group>Ansible</group>
<id>0ea27de5-ef36-4a2f-b09c-1bd548eb78d4</id>
<loglevel>INFO</loglevel>
<name>simple-inline-playbook-user-encryption-and-ssh-pass</name>
<nodeFilterEditable>false</nodeFilterEditable>
<nodefilters>
<filter>name: ssh-node </filter>
</nodefilters>
<nodesSelectedByDefault>true</nodesSelectedByDefault>
<plugins />
<scheduleEnabled>true</scheduleEnabled>
<sequence keepgoing='false' strategy='node-first'>
<command>
<node-step-plugin type='com.batix.rundeck.plugins.AnsiblePlaybookInlineWorkflowNodeStep'>
<configuration>
<entry key='ansible-become' value='false' />
<entry key='ansible-encrypt-extra-vars' value='false' />
<entry key='ansible-extra-param' value='--extra-vars=@/home/rundeck/ansible/user-encrypted-env-vars.yaml' />
<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;' />
<entry key='ansible-ssh-auth-type' value='password' />
<entry key='ansible-ssh-passphrase-option' value='option.password' />
<entry key='ansible-ssh-password-storage-path' value='keys/project/ansible-test/ssh-node.pass' />
<entry key='ansible-ssh-use-agent' value='false' />
<entry key='ansible-ssh-user' value='rundeck' />
<entry key='ansible-vault-storage-path' value='keys/project/ansible-test/vault-user.pass' />
</configuration>
</node-step-plugin>
</command>
</sequence>
<uuid>0ea27de5-ef36-4a2f-b09c-1bd548eb78d4</uuid>
</job>
</joblist>
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ public static AnsibleRunner buildAnsibleRunner(AnsibleRunnerContextBuilder conte
File tempVaultFile ;
File tempSshVarsFile ;
File tempBecameVarsFile ;
File vaultPromptFile;

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

if(ansibleVault==null){
tempInternalVaultFile = AnsibleVault.createVaultScriptAuth("ansible-script-vault");

ansibleVault = AnsibleVault.builder()
.baseDirectory(baseDirectory)
.masterPassword(AnsibleUtil.randomString())
Expand Down Expand Up @@ -387,12 +387,7 @@ public int run() throws Exception {

useAnsibleVault = ansibleVault.checkAnsibleVault();

if(useAnsibleVault) {
tempInternalVaultFile = ansibleVault.getVaultPasswordScriptFile();

procArgs.add("--vault-id");
procArgs.add("internal-encrypt@" + tempInternalVaultFile.getAbsolutePath());
}else{
if(!useAnsibleVault) {
System.err.println("WARN: ansible-vault is not installed, extra-vars will not be encrypted.");
}
}
Expand Down Expand Up @@ -431,12 +426,6 @@ public int run() throws Exception {
procArgs.add("--extra-vars" + "=" + "@" + tempVarsFile.getAbsolutePath());
}

if (vaultPass != null && !vaultPass.isEmpty()) {
tempVaultFile = ansibleVault.getVaultPasswordScriptFile();
procArgs.add("--vault-id");
procArgs.add(tempVaultFile.getAbsolutePath());
}

if (sshPrivateKey != null && !sshPrivateKey.isEmpty()) {
String privateKeyData = sshPrivateKey.replaceAll("\r\n", "\n");
tempPkFile = AnsibleUtil.createTemporaryFile("id_rsa", privateKeyData);
Expand Down Expand Up @@ -506,16 +495,14 @@ public int run() throws Exception {
procArgs.addAll(tokenizeCommand(extraParams));
}

if (debug) {
System.out.println(" procArgs: " + procArgs);
}

if(processExecutorBuilder==null){
processExecutorBuilder = ProcessExecutor.builder();
}

//set main process command
processExecutorBuilder.procArgs(procArgs);
if (debug) {
System.out.println(" procArgs: " + procArgs);
processExecutorBuilder.debug(true);
}

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

processExecutorBuilder.environmentVariables(processEnvironment);

//set STDIN variables
List<String> stdinVariables = new ArrayList<>();
List<VaultPrompt> stdinVariables = new ArrayList<>();

if(useAnsibleVault || vaultPass != null ){
vaultPromptFile = File.createTempFile("vault-prompt", ".log");
}

if (useAnsibleVault) {
stdinVariables.add(ansibleVault.getMasterPassword() + "\n");
VaultPrompt vaultPrompt = VaultPrompt.builder()
.vaultId("internal-encrypt")
.vaultPassword(ansibleVault.getMasterPassword() + "\n")
.build();

stdinVariables.add(vaultPrompt);
processEnvironment.put("LOG_PATH", vaultPromptFile.getAbsolutePath());

tempInternalVaultFile = ansibleVault.getVaultPasswordScriptFile();

procArgs.add("--vault-id");
procArgs.add("internal-encrypt@" + tempInternalVaultFile.getAbsolutePath());
}

if (vaultPass != null && !vaultPass.isEmpty()) {
stdinVariables.add(vaultPass + "\n");
VaultPrompt vaultPrompt = VaultPrompt.builder()
.vaultId("None")
.vaultPassword(vaultPass + "\n")
.build();

stdinVariables.add(vaultPrompt);
processEnvironment.putIfAbsent("LOG_PATH", vaultPromptFile.getAbsolutePath());

tempVaultFile = ansibleVault.getVaultPasswordScriptFile();
procArgs.add("--vault-id");
procArgs.add(tempVaultFile.getAbsolutePath());
}

//set main process command
processExecutorBuilder.procArgs(procArgs);
processExecutorBuilder.stdinVariables(stdinVariables);
processExecutorBuilder.environmentVariables(processEnvironment);

//set vault prompt file
if(vaultPromptFile !=null){
processExecutorBuilder.promptStdinLogFile(vaultPromptFile);
}

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

Expand Down Expand Up @@ -627,6 +645,10 @@ public int run() throws Exception {
tempInternalVaultFile.deleteOnExit();
}

if(vaultPromptFile != null && !vaultPromptFile.delete()){
vaultPromptFile.deleteOnExit();
}

if (usingTempDirectory && !retainTempDirectory) {
deleteTempDirectory(baseDirectory);
}
Expand Down Expand Up @@ -668,9 +690,13 @@ public boolean registerKeySshAgent(String keyPath) throws Exception {
env.put("SSH_ASKPASS", tempPassVarsFile.getAbsolutePath());
}

List<String> stdinVariables = new ArrayList<>();
List<VaultPrompt> stdinVariables = new ArrayList<>();
if (sshPassphrase != null && !sshPassphrase.isEmpty()) {
stdinVariables.add(sshPassphrase + "\n");
VaultPrompt sshPassPrompt = VaultPrompt.builder()
.vaultPassword(sshPassphrase + "\n")
.build();

stdinVariables.add(sshPassPrompt);
}

ProcessExecutor processExecutor = ProcessExecutor.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.rundeck.plugins.ansible.util.AnsibleUtil;
import com.rundeck.plugins.ansible.util.ProcessExecutor;
import com.rundeck.plugins.ansible.util.VaultPrompt;
import lombok.Builder;
import lombok.Data;

Expand Down Expand Up @@ -72,8 +73,11 @@ public String encryptVariable(String key,
}

//send values to STDIN in order
List<String> stdinVariables = new ArrayList<>();
stdinVariables.add(content);
List<VaultPrompt> stdinVariables = new ArrayList<>();
VaultPrompt vaultPrompt = VaultPrompt.builder()
.vaultPassword(content)
.build();
stdinVariables.add(vaultPrompt);

Map<String, String> env = new HashMap<>();
env.put("VAULT_ID_SECRET", masterPassword);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

import lombok.Builder;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.*;
import java.util.List;
import java.io.File;
import java.util.Map;

@Builder
Expand All @@ -18,12 +15,17 @@ public class ProcessExecutor {

private Map<String, String> environmentVariables;

private List<String> stdinVariables;
private List<VaultPrompt> stdinVariables;

private boolean redirectErrorStream;

private File promptStdinLogFile;

private boolean debug;


public Process run() throws IOException {

ProcessBuilder processBuilder = new ProcessBuilder()
.command(procArgs)
.redirectErrorStream(redirectErrorStream);
Expand All @@ -32,6 +34,7 @@ public Process run() throws IOException {
processBuilder.directory(baseDirectory);
}


if(environmentVariables!=null){
Map<String, String> processEnvironment = processBuilder.environment();

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

if (stdinVariables != null) {
try {

for (String stdinVariable : stdinVariables) {
stdinw.write(stdinVariable);
for (VaultPrompt stdinVariable : stdinVariables) {
processPrompt(stdinw, stdinVariable);
}
stdinw.flush();
} catch (Exception e) {
System.err.println("error encryptFileAnsibleVault file " + e.getMessage());
}
}

stdinw.close();
stdin.close();

return proc;
}

private void processPrompt(OutputStreamWriter stdinw, final VaultPrompt vaultPrompt) throws Exception {
if(promptStdinLogFile!=null){
Thread stdinThread = new Thread(() -> {
try {
stdinw.write(vaultPrompt.getVaultPassword());
stdinw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
);

//wait for prompt
boolean promptFound = false;
long start = System.currentTimeMillis();
long end = start + 60 * 1000;
BufferedReader reader = new BufferedReader(new FileReader(promptStdinLogFile));

while (!promptFound && System.currentTimeMillis() < end){
String currentLine = reader.readLine();
if(debug){
System.out.println("waiting for vault password prompt ("+vaultPrompt.getVaultId()+")...");
}
if(currentLine!=null && currentLine.contains("Enter Password ("+vaultPrompt.getVaultId()+"):")){
if(debug) {
System.out.println(currentLine);
}
promptFound = true;
//send password / content
stdinThread.start();
}
Thread.sleep(2000);
}
reader.close();

}else{
stdinw.write(vaultPrompt.getVaultPassword());
stdinw.flush();
}
}

}
13 changes: 13 additions & 0 deletions src/main/groovy/com/rundeck/plugins/ansible/util/VaultPrompt.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.rundeck.plugins.ansible.util;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class VaultPrompt {

private String vaultId;
private String vaultPassword;

}
Loading
Loading