Skip to content

Commit e400eab

Browse files
authored
Merge pull request #35 from rundeck-plugins/nodes-with-key-storage
RUN-3734: Retrieve git password from key storage
2 parents 363c12e + 172df77 commit e400eab

7 files changed

Lines changed: 440 additions & 41 deletions

File tree

README.md

Lines changed: 195 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,111 @@ You need to set up the following options to use the plugin:
5454

5555
### Authentication
5656

57-
* **Git Password**: Password to authenticate remotely
58-
* **SSH: Strict Host Key Checking**: Use strict host key checking.
59-
If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify.
60-
* **SSH Key Path**: SSH Key Path to authenticate
57+
The plugin supports multiple authentication methods. **Key Storage is recommended** for secure credential management.
58+
59+
#### Password Authentication
60+
61+
##### Option 1: Key Storage (Recommended)
62+
* **Git Password Storage Path**: Key storage path for Git password (secure)
63+
64+
**How to use:**
65+
1. Navigate to Rundeck **System Menu****Key Storage**
66+
2. Click **Add or Upload a Key****Password**
67+
3. Enter a path like `keys/git/myrepo-password` and your Git password
68+
4. In the Resource Model configuration, use the **Key Storage browser** to select the password path
69+
70+
**Example paths:**
71+
```
72+
keys/git/github-token # GitHub personal access token
73+
keys/git/gitlab-password # GitLab password
74+
keys/project1/git-auth # Project-specific credentials
75+
```
76+
77+
##### Option 2: Plain Text (Less Secure)
78+
* **Git Password (Plain Text)**: Password to authenticate remotely (not recommended for production)
79+
80+
**Note:** If both are configured, Key Storage takes precedence.
81+
82+
---
83+
84+
#### SSH Key Authentication
85+
86+
##### Option 1: Key Storage (Recommended)
87+
* **SSH Key Storage Path**: SSH Key from Rundeck Key Storage (secure)
88+
89+
**How to use:**
90+
1. Navigate to Rundeck **System Menu****Key Storage**
91+
2. Click **Add or Upload a Key****Private Key**
92+
3. Upload your SSH private key (e.g., `id_rsa`) and save it with a path like `keys/git/ssh-key`
93+
4. In the Resource Model configuration, use the **Key Storage browser** to select the key path
94+
95+
**Example paths:**
96+
```
97+
keys/git/deployment-key # Deployment key for specific repo
98+
keys/git/github-ssh-key # GitHub SSH key
99+
keys/shared/git-readonly-key # Shared read-only access key
100+
```
101+
102+
##### Option 2: Filesystem Path (Legacy)
103+
* **SSH Key Path (Filesystem)**: Path to SSH key file on the Rundeck server filesystem
104+
105+
**Example:** `/home/rundeck/.ssh/id_rsa`
106+
107+
**Note:** If both are configured, Key Storage takes precedence.
108+
109+
##### SSH Host Key Checking
110+
* **SSH: Strict Host Key Checking**:
111+
- `yes` - Require remote host SSH key is defined in `~/.ssh/known_hosts` (more secure)
112+
- `no` - Skip host key verification (less secure, useful for testing)
113+
114+
---
115+
116+
#### Authentication Examples by Git URL Type
117+
118+
| Git URL Type | Recommended Auth | Example URL |
119+
|--------------|------------------|-------------|
120+
| HTTPS with token | Password Storage (token) | `https://github.com/user/repo.git` |
121+
| HTTPS with password | Password Storage | `https://username@github.com/user/repo.git` |
122+
| SSH | SSH Key Storage | `git@github.com:user/repo.git` |
123+
| SSH | SSH Key Storage | `ssh://git@github.com/user/repo.git` |
124+
125+
**Important:** For HTTPS authentication, include the username in the URL: `https://username@host.com/repo.git`
126+
127+
---
128+
129+
#### Troubleshooting Authentication
130+
131+
**Problem: "Authentication failed" error**
132+
- Verify the Key Storage path is correct (e.g., `keys/git/password`, not `/keys/git/password`)
133+
- Ensure the credential exists in Key Storage
134+
- For HTTPS: Include username in Git URL (`https://user@github.com/...`)
135+
- For SSH: Verify host key is in `known_hosts` if strict checking is enabled
136+
137+
**Problem: "storageTree is null" in logs**
138+
- The plugin requires Services API (Rundeck 5.16.0+)
139+
- Fallback to filesystem/plain text options if Key Storage is unavailable
140+
141+
**Problem: SSH authentication fails**
142+
- Verify SSH key format (OpenSSH format, starts with `-----BEGIN RSA PRIVATE KEY-----` or similar)
143+
- Check SSH key permissions if using filesystem path (should be `600`)
144+
- For GitHub/GitLab, ensure the public key is added to your account
145+
- Try with `Strict Host Key Checking = no` for initial testing
146+
147+
**Problem: Key Storage path not found**
148+
- Key Storage paths should start with `keys/` (e.g., `keys/git/password`)
149+
- Use the Key Storage browser in the UI to select the correct path
150+
- Verify the key type matches (password vs private key)
151+
152+
---
153+
154+
#### Security Best Practices
155+
156+
1. **Always use Key Storage** in production environments
157+
2. **Use project-specific keys** when possible (e.g., `keys/project1/git-key`)
158+
3. **Use deployment keys** with minimal permissions for SSH
159+
4. **Use personal access tokens** instead of passwords for HTTPS (GitHub, GitLab, etc.)
160+
5. **Rotate credentials regularly** and update them in Key Storage
161+
6. **Enable strict host key checking** for SSH in production
61162

62163
### Limitations
63164

@@ -86,10 +187,17 @@ This plugin can clone/pull, add, commit, and push a git repository via 4 Workflo
86187

87188
##### Authentication
88189

89-
* **Password Storage Path**: Password storage path to authenticate remotely. This can be an Access Token - such as a Github access token.
90-
* **SSH: Strict Host Key Checking**: Use strict host key checking.
91-
If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify.
92-
* **SSH Key Storage Path**: SSH Key storage path to authenticate
190+
The workflow steps support the same authentication methods as the Resource Model:
191+
192+
* **Password Storage Path**: Key Storage path for Git password or access token (e.g., `keys/git/github-token`)
193+
* **SSH Key Storage Path**: Key Storage path for SSH private key (e.g., `keys/git/ssh-key`)
194+
* **SSH: Strict Host Key Checking**:
195+
- `yes` - Require host key in `~/.ssh/known_hosts` (recommended for production)
196+
- `no` - Skip host key verification (useful for testing)
197+
198+
**Tip:** You can use GitHub/GitLab personal access tokens as passwords for HTTPS authentication.
199+
200+
For detailed authentication setup and troubleshooting, see the [Authentication section](#authentication) above.
93201

94202
### GIT Clone Workflow Step
95203

@@ -156,5 +264,82 @@ You need to set up following additional options to use the plugin:
156264
##### Repo Settings
157265

158266
* **Message**: Commit message to be used. Defaults to `Rundeck Commit`
159-
* **Add**: Adds all contents of the git repo before commiting. Defaults to `False`. If you need to be more specific, please use `GIT / Add` workflow step.
160-
* **Push**: Pushes the repository after commiting the changes. Defaults to `False`.
267+
* **Add**: Adds all contents of the git repo before committing. Defaults to `False`. If you need to be more specific, please use `GIT / Add` workflow step.
268+
* **Push**: Pushes the repository after committing the changes. Defaults to `False`.
269+
270+
---
271+
272+
## Quick Reference
273+
274+
### Key Storage Setup
275+
276+
**For Passwords/Tokens:**
277+
1. System Menu → Key Storage → Add or Upload a Key → **Password**
278+
2. Path format: `keys/git/your-credential-name`
279+
3. Select in plugin using Key Storage browser
280+
281+
**For SSH Keys:**
282+
1. System Menu → Key Storage → Add or Upload a Key → **Private Key**
283+
2. Upload your private key file (e.g., `id_rsa`)
284+
3. Path format: `keys/git/your-key-name`
285+
4. Select in plugin using Key Storage browser
286+
287+
### Common Configuration Scenarios
288+
289+
#### Scenario 1: Public GitHub Repo (Read-Only)
290+
```
291+
Git URL: https://github.com/user/repo.git
292+
Branch: main
293+
Authentication: None required
294+
```
295+
296+
#### Scenario 2: Private GitHub Repo with Personal Access Token
297+
```
298+
Git URL: https://github.com/user/repo.git
299+
Branch: main
300+
Authentication: Git Password Storage Path → keys/git/github-token
301+
(Store GitHub PAT in Key Storage as password)
302+
```
303+
304+
#### Scenario 3: Private GitLab Repo with SSH Key
305+
```
306+
Git URL: git@gitlab.com:user/repo.git
307+
Branch: main
308+
Authentication: SSH Key Storage Path → keys/git/gitlab-ssh-key
309+
Strict Host Key Checking: yes
310+
```
311+
312+
#### Scenario 4: Private Repo with HTTPS Username/Password
313+
```
314+
Git URL: https://username@github.com/user/repo.git
315+
Branch: main
316+
Authentication: Git Password Storage Path → keys/git/password
317+
(Include username in URL)
318+
```
319+
320+
### Property Reference
321+
322+
| Property Name | Description | Example Value |
323+
|--------------|-------------|---------------|
324+
| `gitUrl` | Git repository URL | `https://github.com/user/repo.git` |
325+
| `gitBaseDirectory` | Local checkout directory | `/var/rundeck/git-repos/project1` |
326+
| `gitBranch` | Branch to checkout | `main` or `develop` |
327+
| `gitFile` | Resource model file in repo | `resources.yaml` |
328+
| `gitFormatFile` | File format | `xml`, `yaml`, or `json` |
329+
| `gitPasswordPath` | Plain text password | `mypassword` (not recommended) |
330+
| `gitPasswordPathStorage` | Key Storage path for password | `keys/git/password` |
331+
| `gitKeyPath` | Filesystem SSH key path | `/home/rundeck/.ssh/id_rsa` |
332+
| `gitKeyPathStorage` | Key Storage path for SSH key | `keys/git/ssh-key` |
333+
| `strictHostKeyChecking` | SSH host key verification | `yes` or `no` |
334+
| `writable` | Allow writing to remote | `true` or `false` |
335+
336+
### Version Requirements
337+
338+
- **Rundeck 5.16.0 or later** - Required for Key Storage support
339+
- Earlier versions can use filesystem paths and plain text authentication
340+
341+
### Support
342+
343+
For issues or questions:
344+
- GitHub Issues: [rundeck-plugins/git-plugin](https://github.com/rundeck-plugins/git-plugin/issues)
345+
- Rundeck Documentation: [https://docs.rundeck.com](https://docs.rundeck.com)

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ junit = "4.13.2"
88
rundeckCore = "5.16.0-20251006"
99
slf4j = "1.7.36"
1010
jgit = "6.6.1.202309021850-r"
11-
jgitSsh = "5.13.3.202401111512-r"
11+
jgitSsh = "6.6.1.202309021850-r"
1212
spock = "2.0-groovy-3.0"
1313
cglib = "3.3.0"
1414
objenesis = "1.4"

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

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ import com.dtolabs.rundeck.core.resources.format.ResourceFormatParser
1010
import com.dtolabs.rundeck.core.resources.format.ResourceFormatParserException
1111
import com.dtolabs.rundeck.core.resources.format.UnsupportedFormatException
1212
import com.dtolabs.utils.Streams
13-
13+
import com.dtolabs.rundeck.core.execution.ExecutionContext
14+
import com.dtolabs.rundeck.core.execution.ExecutionContextImpl
15+
import com.rundeck.plugin.util.GitPluginUtil
16+
import groovy.transform.CompileStatic
17+
import org.rundeck.app.spi.Services
18+
import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree
1419

1520
/**
1621
* Created by luistoledo on 12/18/17.
1722
*/
23+
@CompileStatic
1824
class GitResourceModel implements ResourceModelSource , WriteableModelSource{
1925

2026
private Properties configuration;
@@ -31,8 +37,15 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
3137
this.writable=true;
3238
}
3339

40+
GitResourceModel(Services services, Properties configuration, Framework framework) {
41+
configure(configuration,framework,services)
42+
}
3443

3544
GitResourceModel(Properties configuration, Framework framework) {
45+
configure(configuration,framework, null)
46+
}
47+
48+
def configure(Properties configuration, Framework framework, Services services){
3649
this.configuration = configuration
3750
this.framework = framework
3851

@@ -45,14 +58,41 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
4558
gitManager = new GitManager(configuration)
4659
}
4760

48-
if(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE)) {
49-
gitManager.setGitPassword(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE))
61+
// Plain text password (less secure, checked first)
62+
// Support old property name for backwards compatibility
63+
if(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_PATH)) {
64+
gitManager.setGitPassword(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_PATH))
65+
}
66+
67+
// SSH Key from filesystem path (checked first)
68+
if(configuration.getProperty(GitResourceModelFactory.GIT_KEY_PATH)) {
69+
gitManager.setSshPrivateKeyPath(configuration.getProperty(GitResourceModelFactory.GIT_KEY_PATH))
5070
}
5171

52-
if(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE)) {
53-
gitManager.setSshPrivateKeyPath(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE))
72+
// Create execution context once for Key Storage operations
73+
ExecutionContext context = null
74+
if (services) {
75+
context = new ExecutionContextImpl.Builder()
76+
.framework(framework)
77+
.storageTree(services.getService(KeyStorageTree.class))
78+
.build()
5479
}
5580

81+
// Key Storage password (more secure, takes precedence if both are set)
82+
if(context && configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE_PATH)){
83+
def password = GitPluginUtil.getFromKeyStorage(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE_PATH), context)
84+
if (password != null) {
85+
gitManager.setGitPassword(password)
86+
}
87+
}
88+
89+
// SSH Key from Key Storage (takes precedence if both are set)
90+
if(context && configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE_PATH)){
91+
def sshKey = GitPluginUtil.getFromKeyStorage(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE_PATH), context)
92+
if (sshKey != null) {
93+
gitManager.setSshPrivateKey(sshKey)
94+
}
95+
}
5696
}
5797

5898
@Override
@@ -80,7 +120,6 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
80120
throw new ResourceModelSourceException(
81121
"Error requesting Resource Model Source from GIT, " +e.getMessage(),e);
82122
}
83-
return null
84123
}
85124

86125
private ResourceFormatParser getResourceFormatParser() throws UnsupportedFormatException {
@@ -98,18 +137,18 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
98137
}
99138

100139
@Override
101-
public SourceType getSourceType() {
140+
SourceType getSourceType() {
102141
return writable ? SourceType.READ_WRITE : SourceType.READ_ONLY;
103142
}
104143

105144
@Override
106-
public WriteableModelSource getWriteable() {
145+
WriteableModelSource getWriteable() {
107146
return writable ? this : null;
108147
}
109148

110149

111150
@Override
112-
public String getSyntaxMimeType() {
151+
String getSyntaxMimeType() {
113152
try {
114153
return getResourceFormatParser().getPreferredMimeType();
115154
} catch (UnsupportedFormatException e) {
@@ -140,7 +179,7 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
140179
}
141180

142181
@Override
143-
public long writeData(InputStream data) throws IOException, ResourceModelSourceException {
182+
long writeData(InputStream data) throws IOException, ResourceModelSourceException {
144183
if (!writable) {
145184
throw new IllegalArgumentException("Cannot write to file, it is not configured to be writeable");
146185
}
@@ -172,7 +211,7 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{
172211
}
173212

174213
@Override
175-
public String getSourceDescription() {
214+
String getSourceDescription() {
176215
String gitURL=configuration.getProperty(GitResourceModelFactory.GIT_URL)
177216
return "Git repo: "+gitURL+", file:"+this.fileName;
178217
}

0 commit comments

Comments
 (0)