Skip to content

Commit c3eff80

Browse files
committed
fix: address Copilot PR review comments
- Fix UI labels to distinguish plain text vs Key Storage password fields - Reverse precedence: Key Storage password now takes precedence over plain text - Add null safety check before setting password from Key Storage - Fix NullPointerException by reusing storageTree variable - Add error handling with try-catch and logging for Key Storage access - Add JavaDoc documentation to getFromKeyStorage method - Use explicit UTF-8 encoding for password strings - Update README with Key Storage authentication documentation - Clean up test file whitespace All changes maintain backwards compatibility - no breaking changes to property names.
1 parent b9215c5 commit c3eff80

5 files changed

Lines changed: 56 additions & 25 deletions

File tree

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,23 @@ You need to set up the following options to use the plugin:
5454

5555
### Authentication
5656

57-
* **Git Password**: Password to authenticate remotely
57+
The plugin supports multiple authentication methods:
58+
59+
#### Password Authentication
60+
* **Git Password (Plain Text)**: Password to authenticate remotely (plain text - less secure)
61+
* **Git Password Storage Path**: Key storage path for Git password (more secure - recommended)
62+
63+
If both password fields are configured, the Key Storage path takes precedence for security.
64+
65+
#### SSH Key Authentication
5866
* **SSH: Strict Host Key Checking**: Use strict host key checking.
5967
If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify.
6068
* **SSH Key Path**: SSH Key Path to authenticate
6169

70+
**Recommended:** Use Key Storage for passwords instead of plain text. To use Key Storage:
71+
1. Store your password in Rundeck Key Storage (under `keys/` path)
72+
2. Select the password path using the Key Storage browser in the plugin configuration
73+
6274
### Limitations
6375

6476
* The plugin needs to clone the full repo on the local directory path (Base Directory option) to get the file that will be added to the resource model.

src/main/groovy/com/rundeck/plugin/GitResourceModel.groovy

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,23 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
5858
gitManager = new GitManager(configuration)
5959
}
6060

61+
// Plain text password (less secure, checked first)
62+
// Support old property name for backwards compatibility
63+
if(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE)) {
64+
gitManager.setGitPassword(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE))
65+
}
66+
67+
// Key Storage password (more secure, takes precedence if both are set)
6168
if(services && configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE_PATH)){
6269
ExecutionContext context = new ExecutionContextImpl.Builder()
6370
.framework(framework)
6471
.storageTree(services.getService(KeyStorageTree.class))
6572
.build();
6673

6774
def password = GitPluginUtil.getFromKeyStorage(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE_PATH), context)
68-
gitManager.setGitPassword(password)
69-
}
70-
71-
if(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE)) {
72-
gitManager.setGitPassword(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE))
75+
if (password != null) {
76+
gitManager.setGitPassword(password)
77+
}
7378
}
7479

7580
if(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE)) {

src/main/groovy/com/rundeck/plugin/GitResourceModelFactory.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ Some examples:
8080
.property(PropertyUtil.bool(WRITABLE, "Writable",
8181
"Allow to write the remote file.",
8282
false,"false",null,renderingOptionsConfig))
83-
.property(PropertyUtil.string(GIT_PASSWORD_STORAGE, "Git Password", 'Password to authenticate remotely', false,
83+
.property(PropertyUtil.string(GIT_PASSWORD_STORAGE, "Git Password (Plain Text)", 'Password to authenticate remotely (plain text)', false,
8484
null,null,null, renderingOptionsAuthenticationPassword))
85-
.property(PropertyUtil.string(GIT_PASSWORD_STORAGE_PATH, "Git Password", 'Password Storage to authenticate remotely', false,
85+
.property(PropertyUtil.string(GIT_PASSWORD_STORAGE_PATH, "Git Password Storage Path", 'Key storage path for Git password to authenticate remotely', false,
8686
null,null,null, renderingOptionsAuthenticationPasswordStorage))
8787
.property(PropertyUtil.select(GIT_HOSTKEY_CHECKING, "SSH: Strict Host Key Checking", '''Use strict host key checking.
8888
If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify.''', false,

src/main/groovy/com/rundeck/plugin/util/GitPluginUtil.groovy

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.dtolabs.rundeck.core.execution.ExecutionContextImpl
77
import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree;
88
import com.dtolabs.rundeck.core.execution.ExecutionListener
99
import groovy.transform.CompileStatic
10+
import java.nio.charset.StandardCharsets
1011

1112
/**
1213
* Created by luistoledo on 12/18/17.
@@ -40,27 +41,45 @@ class GitPluginUtil {
4041
ResourceMeta contents = context.getExecutionContext().getStorageTree().getResource(path).getContents();
4142
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
4243
contents.writeContent(byteArrayOutputStream);
43-
String password = new String(byteArrayOutputStream.toByteArray());
44+
String password = new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);
4445

4546
return password;
4647

4748
}
4849

49-
static String getFromKeyStorage(String path, ExecutionContextImpl context){
50+
/**
51+
* Retrieves the contents of a resource from the key storage using the provided path and execution context.
52+
* <p>
53+
* If the storage tree is available, this method attempts to read the resource at the given path and
54+
* returns its contents as a String. If the storage tree is null or an error occurs, it logs a message and returns null.
55+
*
56+
* @param path the path to the resource in the key storage
57+
* @param context the Rundeck execution context
58+
* @return the contents of the resource as a String, or null if the storage tree is null or an error occurs
59+
*/
60+
static String getFromKeyStorage(String path, ExecutionContextImpl context){
5061
KeyStorageTree storageTree = (KeyStorageTree)context.getStorageTree()
5162

52-
if (storageTree!=null){
53-
ResourceMeta contents = context.getStorageTree().getResource(path).getContents();
54-
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
55-
contents.writeContent(byteArrayOutputStream);
56-
String password = new String(byteArrayOutputStream.toByteArray());
57-
58-
return password;
59-
} else {
63+
if (storageTree == null){
6064
ExecutionListener logger = context.getExecutionListener()
61-
logger.log(1, "storageTree is null. Cannot retrieve password");
65+
logger.log(1, "storageTree is null. Cannot retrieve password from Key Storage.");
6266
return null
6367
}
6468

69+
try {
70+
ResourceMeta contents = storageTree.getResource(path).getContents();
71+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
72+
try {
73+
contents.writeContent(byteArrayOutputStream);
74+
String password = new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);
75+
return password;
76+
} finally {
77+
byteArrayOutputStream.close();
78+
}
79+
} catch (Exception e) {
80+
ExecutionListener logger = context.getExecutionListener()
81+
logger.log(1, "Failed to retrieve password from Key Storage at path '${path}': ${e.message}");
82+
return null
83+
}
6584
}
6685
}

src/test/groovy/com/rundeck/plugin/GitResourceModelSpec.groovy

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,6 @@ class GitResourceModelSpec extends Specification{
170170
def nodeSet = Mock(INodeSet)
171171
def framework = getFramework(nodeSet)
172172

173-
174-
175173
String path = "resources"
176174
String fileName = "resources.xml"
177175
String format = "xml"
@@ -185,7 +183,7 @@ class GitResourceModelSpec extends Specification{
185183
gitBaseDirectory:path,
186184
gitFormatFile:format,
187185
gitFile:fileName,
188-
gitPasswordPathStorage:"gitPassword",
186+
gitPasswordPathStorage:"keys/git/password",
189187
]
190188

191189
def gitManager = Mock(GitManager)
@@ -216,9 +214,6 @@ class GitResourceModelSpec extends Specification{
216214
then:
217215
1 * gitManager.getFile(path) >> inputStream
218216
result == nodeSet
219-
220-
221-
222217
}
223218

224219

0 commit comments

Comments
 (0)