diff --git a/README.md b/README.md index 7528aa0..addfb03 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,111 @@ You need to set up the following options to use the plugin: ### Authentication -* **Git Password**: Password to authenticate remotely -* **SSH: Strict Host Key Checking**: Use strict host key checking. -If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify. -* **SSH Key Path**: SSH Key Path to authenticate +The plugin supports multiple authentication methods. **Key Storage is recommended** for secure credential management. + +#### Password Authentication + +##### Option 1: Key Storage (Recommended) +* **Git Password Storage Path**: Key storage path for Git password (secure) + +**How to use:** +1. Navigate to Rundeck **System Menu** → **Key Storage** +2. Click **Add or Upload a Key** → **Password** +3. Enter a path like `keys/git/myrepo-password` and your Git password +4. In the Resource Model configuration, use the **Key Storage browser** to select the password path + +**Example paths:** +``` +keys/git/github-token # GitHub personal access token +keys/git/gitlab-password # GitLab password +keys/project1/git-auth # Project-specific credentials +``` + +##### Option 2: Plain Text (Less Secure) +* **Git Password (Plain Text)**: Password to authenticate remotely (not recommended for production) + +**Note:** If both are configured, Key Storage takes precedence. + +--- + +#### SSH Key Authentication + +##### Option 1: Key Storage (Recommended) +* **SSH Key Storage Path**: SSH Key from Rundeck Key Storage (secure) + +**How to use:** +1. Navigate to Rundeck **System Menu** → **Key Storage** +2. Click **Add or Upload a Key** → **Private Key** +3. Upload your SSH private key (e.g., `id_rsa`) and save it with a path like `keys/git/ssh-key` +4. In the Resource Model configuration, use the **Key Storage browser** to select the key path + +**Example paths:** +``` +keys/git/deployment-key # Deployment key for specific repo +keys/git/github-ssh-key # GitHub SSH key +keys/shared/git-readonly-key # Shared read-only access key +``` + +##### Option 2: Filesystem Path (Legacy) +* **SSH Key Path (Filesystem)**: Path to SSH key file on the Rundeck server filesystem + +**Example:** `/home/rundeck/.ssh/id_rsa` + +**Note:** If both are configured, Key Storage takes precedence. + +##### SSH Host Key Checking +* **SSH: Strict Host Key Checking**: + - `yes` - Require remote host SSH key is defined in `~/.ssh/known_hosts` (more secure) + - `no` - Skip host key verification (less secure, useful for testing) + +--- + +#### Authentication Examples by Git URL Type + +| Git URL Type | Recommended Auth | Example URL | +|--------------|------------------|-------------| +| HTTPS with token | Password Storage (token) | `https://github.com/user/repo.git` | +| HTTPS with password | Password Storage | `https://username@github.com/user/repo.git` | +| SSH | SSH Key Storage | `git@github.com:user/repo.git` | +| SSH | SSH Key Storage | `ssh://git@github.com/user/repo.git` | + +**Important:** For HTTPS authentication, include the username in the URL: `https://username@host.com/repo.git` + +--- + +#### Troubleshooting Authentication + +**Problem: "Authentication failed" error** +- Verify the Key Storage path is correct (e.g., `keys/git/password`, not `/keys/git/password`) +- Ensure the credential exists in Key Storage +- For HTTPS: Include username in Git URL (`https://user@github.com/...`) +- For SSH: Verify host key is in `known_hosts` if strict checking is enabled + +**Problem: "storageTree is null" in logs** +- The plugin requires Services API (Rundeck 5.16.0+) +- Fallback to filesystem/plain text options if Key Storage is unavailable + +**Problem: SSH authentication fails** +- Verify SSH key format (OpenSSH format, starts with `-----BEGIN RSA PRIVATE KEY-----` or similar) +- Check SSH key permissions if using filesystem path (should be `600`) +- For GitHub/GitLab, ensure the public key is added to your account +- Try with `Strict Host Key Checking = no` for initial testing + +**Problem: Key Storage path not found** +- Key Storage paths should start with `keys/` (e.g., `keys/git/password`) +- Use the Key Storage browser in the UI to select the correct path +- Verify the key type matches (password vs private key) + +--- + +#### Security Best Practices + +1. **Always use Key Storage** in production environments +2. **Use project-specific keys** when possible (e.g., `keys/project1/git-key`) +3. **Use deployment keys** with minimal permissions for SSH +4. **Use personal access tokens** instead of passwords for HTTPS (GitHub, GitLab, etc.) +5. **Rotate credentials regularly** and update them in Key Storage +6. **Enable strict host key checking** for SSH in production ### Limitations @@ -86,10 +187,17 @@ This plugin can clone/pull, add, commit, and push a git repository via 4 Workflo ##### Authentication -* **Password Storage Path**: Password storage path to authenticate remotely. This can be an Access Token - such as a Github access token. -* **SSH: Strict Host Key Checking**: Use strict host key checking. -If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify. -* **SSH Key Storage Path**: SSH Key storage path to authenticate +The workflow steps support the same authentication methods as the Resource Model: + +* **Password Storage Path**: Key Storage path for Git password or access token (e.g., `keys/git/github-token`) +* **SSH Key Storage Path**: Key Storage path for SSH private key (e.g., `keys/git/ssh-key`) +* **SSH: Strict Host Key Checking**: + - `yes` - Require host key in `~/.ssh/known_hosts` (recommended for production) + - `no` - Skip host key verification (useful for testing) + +**Tip:** You can use GitHub/GitLab personal access tokens as passwords for HTTPS authentication. + +For detailed authentication setup and troubleshooting, see the [Authentication section](#authentication) above. ### GIT Clone Workflow Step @@ -156,5 +264,82 @@ You need to set up following additional options to use the plugin: ##### Repo Settings * **Message**: Commit message to be used. Defaults to `Rundeck Commit` -* **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. -* **Push**: Pushes the repository after commiting the changes. Defaults to `False`. \ No newline at end of file +* **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. +* **Push**: Pushes the repository after committing the changes. Defaults to `False`. + +--- + +## Quick Reference + +### Key Storage Setup + +**For Passwords/Tokens:** +1. System Menu → Key Storage → Add or Upload a Key → **Password** +2. Path format: `keys/git/your-credential-name` +3. Select in plugin using Key Storage browser + +**For SSH Keys:** +1. System Menu → Key Storage → Add or Upload a Key → **Private Key** +2. Upload your private key file (e.g., `id_rsa`) +3. Path format: `keys/git/your-key-name` +4. Select in plugin using Key Storage browser + +### Common Configuration Scenarios + +#### Scenario 1: Public GitHub Repo (Read-Only) +``` +Git URL: https://github.com/user/repo.git +Branch: main +Authentication: None required +``` + +#### Scenario 2: Private GitHub Repo with Personal Access Token +``` +Git URL: https://github.com/user/repo.git +Branch: main +Authentication: Git Password Storage Path → keys/git/github-token +(Store GitHub PAT in Key Storage as password) +``` + +#### Scenario 3: Private GitLab Repo with SSH Key +``` +Git URL: git@gitlab.com:user/repo.git +Branch: main +Authentication: SSH Key Storage Path → keys/git/gitlab-ssh-key +Strict Host Key Checking: yes +``` + +#### Scenario 4: Private Repo with HTTPS Username/Password +``` +Git URL: https://username@github.com/user/repo.git +Branch: main +Authentication: Git Password Storage Path → keys/git/password +(Include username in URL) +``` + +### Property Reference + +| Property Name | Description | Example Value | +|--------------|-------------|---------------| +| `gitUrl` | Git repository URL | `https://github.com/user/repo.git` | +| `gitBaseDirectory` | Local checkout directory | `/var/rundeck/git-repos/project1` | +| `gitBranch` | Branch to checkout | `main` or `develop` | +| `gitFile` | Resource model file in repo | `resources.yaml` | +| `gitFormatFile` | File format | `xml`, `yaml`, or `json` | +| `gitPasswordPath` | Plain text password | `mypassword` (not recommended) | +| `gitPasswordPathStorage` | Key Storage path for password | `keys/git/password` | +| `gitKeyPath` | Filesystem SSH key path | `/home/rundeck/.ssh/id_rsa` | +| `gitKeyPathStorage` | Key Storage path for SSH key | `keys/git/ssh-key` | +| `strictHostKeyChecking` | SSH host key verification | `yes` or `no` | +| `writable` | Allow writing to remote | `true` or `false` | + +### Version Requirements + +- **Rundeck 5.16.0 or later** - Required for Key Storage support +- Earlier versions can use filesystem paths and plain text authentication + +### Support + +For issues or questions: +- GitHub Issues: [rundeck-plugins/git-plugin](https://github.com/rundeck-plugins/git-plugin/issues) +- Rundeck Documentation: [https://docs.rundeck.com](https://docs.rundeck.com) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f32098c..b99b02c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ junit = "4.13.2" rundeckCore = "5.16.0-20251006" slf4j = "1.7.36" jgit = "6.6.1.202309021850-r" -jgitSsh = "5.13.3.202401111512-r" +jgitSsh = "6.6.1.202309021850-r" spock = "2.0-groovy-3.0" cglib = "3.3.0" objenesis = "1.4" diff --git a/src/main/groovy/com/rundeck/plugin/GitResourceModel.groovy b/src/main/groovy/com/rundeck/plugin/GitResourceModel.groovy index c1f19ad..62669b2 100644 --- a/src/main/groovy/com/rundeck/plugin/GitResourceModel.groovy +++ b/src/main/groovy/com/rundeck/plugin/GitResourceModel.groovy @@ -10,11 +10,17 @@ import com.dtolabs.rundeck.core.resources.format.ResourceFormatParser import com.dtolabs.rundeck.core.resources.format.ResourceFormatParserException import com.dtolabs.rundeck.core.resources.format.UnsupportedFormatException import com.dtolabs.utils.Streams - +import com.dtolabs.rundeck.core.execution.ExecutionContext +import com.dtolabs.rundeck.core.execution.ExecutionContextImpl +import com.rundeck.plugin.util.GitPluginUtil +import groovy.transform.CompileStatic +import org.rundeck.app.spi.Services +import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree /** * Created by luistoledo on 12/18/17. */ +@CompileStatic class GitResourceModel implements ResourceModelSource , WriteableModelSource{ private Properties configuration; @@ -31,8 +37,15 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{ this.writable=true; } + GitResourceModel(Services services, Properties configuration, Framework framework) { + configure(configuration,framework,services) + } GitResourceModel(Properties configuration, Framework framework) { + configure(configuration,framework, null) + } + + def configure(Properties configuration, Framework framework, Services services){ this.configuration = configuration this.framework = framework @@ -45,14 +58,41 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{ gitManager = new GitManager(configuration) } - if(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE)) { - gitManager.setGitPassword(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE)) + // Plain text password (less secure, checked first) + // Support old property name for backwards compatibility + if(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_PATH)) { + gitManager.setGitPassword(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_PATH)) + } + + // SSH Key from filesystem path (checked first) + if(configuration.getProperty(GitResourceModelFactory.GIT_KEY_PATH)) { + gitManager.setSshPrivateKeyPath(configuration.getProperty(GitResourceModelFactory.GIT_KEY_PATH)) } - if(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE)) { - gitManager.setSshPrivateKeyPath(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE)) + // Create execution context once for Key Storage operations + ExecutionContext context = null + if (services) { + context = new ExecutionContextImpl.Builder() + .framework(framework) + .storageTree(services.getService(KeyStorageTree.class)) + .build() } + // Key Storage password (more secure, takes precedence if both are set) + if(context && configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE_PATH)){ + def password = GitPluginUtil.getFromKeyStorage(configuration.getProperty(GitResourceModelFactory.GIT_PASSWORD_STORAGE_PATH), context) + if (password != null) { + gitManager.setGitPassword(password) + } + } + + // SSH Key from Key Storage (takes precedence if both are set) + if(context && configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE_PATH)){ + def sshKey = GitPluginUtil.getFromKeyStorage(configuration.getProperty(GitResourceModelFactory.GIT_KEY_STORAGE_PATH), context) + if (sshKey != null) { + gitManager.setSshPrivateKey(sshKey) + } + } } @Override @@ -80,7 +120,6 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{ throw new ResourceModelSourceException( "Error requesting Resource Model Source from GIT, " +e.getMessage(),e); } - return null } private ResourceFormatParser getResourceFormatParser() throws UnsupportedFormatException { @@ -98,18 +137,18 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{ } @Override - public SourceType getSourceType() { + SourceType getSourceType() { return writable ? SourceType.READ_WRITE : SourceType.READ_ONLY; } @Override - public WriteableModelSource getWriteable() { + WriteableModelSource getWriteable() { return writable ? this : null; } @Override - public String getSyntaxMimeType() { + String getSyntaxMimeType() { try { return getResourceFormatParser().getPreferredMimeType(); } catch (UnsupportedFormatException e) { @@ -140,7 +179,7 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{ } @Override - public long writeData(InputStream data) throws IOException, ResourceModelSourceException { + long writeData(InputStream data) throws IOException, ResourceModelSourceException { if (!writable) { throw new IllegalArgumentException("Cannot write to file, it is not configured to be writeable"); } @@ -172,7 +211,7 @@ class GitResourceModel implements ResourceModelSource , WriteableModelSource{ } @Override - public String getSourceDescription() { + String getSourceDescription() { String gitURL=configuration.getProperty(GitResourceModelFactory.GIT_URL) return "Git repo: "+gitURL+", file:"+this.fileName; } diff --git a/src/main/groovy/com/rundeck/plugin/GitResourceModelFactory.groovy b/src/main/groovy/com/rundeck/plugin/GitResourceModelFactory.groovy index 7c23c88..0f4efdf 100644 --- a/src/main/groovy/com/rundeck/plugin/GitResourceModelFactory.groovy +++ b/src/main/groovy/com/rundeck/plugin/GitResourceModelFactory.groovy @@ -12,12 +12,13 @@ import com.dtolabs.rundeck.plugins.ServiceNameConstants import com.dtolabs.rundeck.plugins.descriptions.PluginDescription import com.dtolabs.rundeck.plugins.util.DescriptionBuilder import com.rundeck.plugin.util.GitPluginUtil +import org.rundeck.app.spi.Services; /** * Created by luistoledo on 12/18/17. */ -@Plugin(name = GitResourceModelFactory.PROVIDER_NAME, service = ServiceNameConstants.ResourceModelSource) -@PluginDescription(title = GitResourceModelFactory.PROVIDER_TITLE, description = GitResourceModelFactory.PROVIDER_DESCRIPTION) +@Plugin(name = PROVIDER_NAME, service = ServiceNameConstants.ResourceModelSource) +@PluginDescription(title = PROVIDER_TITLE, description = PROVIDER_DESCRIPTION) class GitResourceModelFactory implements ResourceModelSourceFactory,Describable { private Framework framework; @@ -36,13 +37,18 @@ class GitResourceModelFactory implements ResourceModelSourceFactory,Describable public final static String GIT_FORMAT_FILE="gitFormatFile" public final static String GIT_BRANCH="gitBranch" public final static String GIT_HOSTKEY_CHECKING="strictHostKeyChecking" - public final static String GIT_KEY_STORAGE="gitKeyPath" - public final static String GIT_PASSWORD_STORAGE="gitPasswordPath" + public final static String GIT_KEY_PATH="gitKeyPath" + public final static String GIT_KEY_STORAGE_PATH="gitKeyPathStorage" + public final static String GIT_PASSWORD_PATH="gitPasswordPath" + public final static String GIT_PASSWORD_STORAGE_PATH="gitPasswordPathStorage" + public static final String WRITABLE="writable"; final static Map renderingOptionsAuthentication = GitPluginUtil.getRenderOpt("Authentication",false) final static Map renderingOptionsAuthenticationPassword = GitPluginUtil.getRenderOpt("Authentication",false, true) + final static Map renderingOptionsAuthenticationPasswordStorage = GitPluginUtil.getRenderOpt("Authentication",false, false, true) + final static Map renderingOptionsAuthenticationKeyStorage = GitPluginUtil.getRenderOpt("Authentication",false, false, false, true) final static Map renderingOptionsConfig = GitPluginUtil.getRenderOpt("Configuration",false) GitResourceModelFactory(Framework framework) { @@ -72,21 +78,23 @@ Some examples: .property(PropertyUtil.string(GIT_FILE, "Resource model File", "Resource model file inside the github repo.", true, null,null,null, renderingOptionsConfig)) .property(PropertyUtil.select(GIT_FORMAT_FILE, "File Format", 'File Format', true, - "xml",GitResourceModelFactory.LIST_FILE_TYPE,null, renderingOptionsConfig)) + "xml", LIST_FILE_TYPE,null, renderingOptionsConfig)) .property(PropertyUtil.bool(WRITABLE, "Writable", "Allow to write the remote file.", false,"false",null,renderingOptionsConfig)) - .property(PropertyUtil.string(GIT_PASSWORD_STORAGE, "Git Password", 'Password to authenticate remotely', false, + .property(PropertyUtil.string(GIT_PASSWORD_PATH, "Git Password (Plain Text)", 'Password to authenticate remotely (plain text)', false, null,null,null, renderingOptionsAuthenticationPassword)) + .property(PropertyUtil.string(GIT_PASSWORD_STORAGE_PATH, "Git Password Storage Path", 'Key storage path for Git password to authenticate remotely', false, + null,null,null, renderingOptionsAuthenticationPasswordStorage)) .property(PropertyUtil.select(GIT_HOSTKEY_CHECKING, "SSH: Strict Host Key Checking", '''Use strict host key checking. If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` file, otherwise do not verify.''', false, - "yes",GitResourceModelFactory.LIST_HOSTKEY_CHECKING,null, renderingOptionsAuthentication)) - .property(PropertyUtil.string(GIT_KEY_STORAGE, "SSH Key Path", 'SSH Key Path', false, + "yes", LIST_HOSTKEY_CHECKING,null, renderingOptionsAuthentication)) + .property(PropertyUtil.string(GIT_KEY_PATH, "SSH Key Path (Filesystem)", 'SSH Key Path from filesystem', false, null,null,null, renderingOptionsAuthentication)) + .property(PropertyUtil.string(GIT_KEY_STORAGE_PATH, "SSH Key Storage Path", 'SSH Key storage path from Rundeck Key Storage', false, + null,null,null, renderingOptionsAuthenticationKeyStorage)) .build() - - @Override Description getDescription() { return DESCRIPTION @@ -94,8 +102,11 @@ If `yes`, require remote host SSH key is defined in the `~/.ssh/known_hosts` fil @Override ResourceModelSource createResourceModelSource(Properties configuration) throws ConfigurationException { - final GitResourceModel resource = new GitResourceModel(configuration,framework) + return new GitResourceModel(configuration,framework) + } - return resource + @Override + ResourceModelSource createResourceModelSource(final Services services, final Properties configuration) throws ConfigurationException { + return new GitResourceModel(services, configuration,framework) } } diff --git a/src/main/groovy/com/rundeck/plugin/util/GitPluginUtil.groovy b/src/main/groovy/com/rundeck/plugin/util/GitPluginUtil.groovy index fdd5ab7..b4a5d6d 100644 --- a/src/main/groovy/com/rundeck/plugin/util/GitPluginUtil.groovy +++ b/src/main/groovy/com/rundeck/plugin/util/GitPluginUtil.groovy @@ -3,10 +3,16 @@ package com.rundeck.plugin.util import com.dtolabs.rundeck.core.plugins.configuration.StringRenderingConstants import com.dtolabs.rundeck.core.storage.ResourceMeta import com.dtolabs.rundeck.plugins.step.PluginStepContext +import com.dtolabs.rundeck.core.execution.ExecutionContext +import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree +import com.dtolabs.rundeck.core.execution.ExecutionListener +import groovy.transform.CompileStatic +import java.nio.charset.StandardCharsets /** * Created by luistoledo on 12/18/17. */ +@CompileStatic class GitPluginUtil { static Map getRenderOpt(String value, boolean secondary, boolean password = false, boolean storagePassword = false, boolean storageKey = false) { Map ret = new HashMap<>(); @@ -31,14 +37,66 @@ class GitPluginUtil { return ret; } + /** + * Reads the contents of a ResourceMeta and returns it as a String. + * + * @param contents the ResourceMeta to read + * @return the contents as a UTF-8 String + * @throws IOException if an error occurs reading the contents + */ + private static String readResourceMetaAsString(ResourceMeta contents) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + try { + contents.writeContent(byteArrayOutputStream); + return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8); + } finally { + byteArrayOutputStream.close(); + } + } + + /** + * Retrieves the contents of a resource from the key storage using the provided path and plugin step context. + * + * @param path the path to the resource in the key storage + * @param context the Rundeck plugin step context + * @return the contents of the resource as a String + * @throws Exception if the resource cannot be found or an error occurs reading the contents + */ static String getFromKeyStorage(String path, PluginStepContext context){ ResourceMeta contents = context.getExecutionContext().getStorageTree().getResource(path).getContents(); - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - contents.writeContent(byteArrayOutputStream); - String password = new String(byteArrayOutputStream.toByteArray()); + return readResourceMetaAsString(contents); + } - return password; + /** + * Retrieves the contents of a resource from the key storage using the provided path and execution context. + *

+ * If the storage tree is available, this method attempts to read the resource at the given path and + * returns its contents as a String. If the storage tree is null or an error occurs, it logs a message and returns null. + * + * @param path the path to the resource in the key storage + * @param context the Rundeck execution context + * @return the contents of the resource as a String, or null if the storage tree is null or an error occurs + */ + static String getFromKeyStorage(String path, ExecutionContext context){ + KeyStorageTree storageTree = (KeyStorageTree)context.getStorageTree() - } + if (storageTree == null){ + ExecutionListener logger = context.getExecutionListener() + if (logger != null) { + logger.log(1, "storageTree is null. Cannot retrieve credential from Key Storage."); + } + return null + } + try { + ResourceMeta contents = storageTree.getResource(path).getContents(); + return readResourceMetaAsString(contents); + } catch (Exception e) { + ExecutionListener logger = context.getExecutionListener() + if (logger != null) { + logger.log(1, "Failed to retrieve credential from Key Storage at path '${path}': ${e.message}"); + } + return null + } + } } diff --git a/src/main/groovy/com/rundeck/plugin/util/PluginSshSessionFactory.groovy b/src/main/groovy/com/rundeck/plugin/util/PluginSshSessionFactory.groovy index e2c87fb..1c77aab 100644 --- a/src/main/groovy/com/rundeck/plugin/util/PluginSshSessionFactory.groovy +++ b/src/main/groovy/com/rundeck/plugin/util/PluginSshSessionFactory.groovy @@ -4,8 +4,8 @@ import com.jcraft.jsch.JSch import com.jcraft.jsch.JSchException import com.jcraft.jsch.Session import org.eclipse.jgit.api.TransportConfigCallback -import org.eclipse.jgit.transport.JschConfigSessionFactory -import org.eclipse.jgit.transport.OpenSshConfig +import org.eclipse.jgit.transport.ssh.jsch.JschConfigSessionFactory +import org.eclipse.jgit.transport.ssh.jsch.OpenSshConfig import org.eclipse.jgit.transport.SshTransport import org.eclipse.jgit.transport.Transport import org.eclipse.jgit.util.FS diff --git a/src/test/groovy/com/rundeck/plugin/GitResourceModelSpec.groovy b/src/test/groovy/com/rundeck/plugin/GitResourceModelSpec.groovy index b3f869f..7180432 100644 --- a/src/test/groovy/com/rundeck/plugin/GitResourceModelSpec.groovy +++ b/src/test/groovy/com/rundeck/plugin/GitResourceModelSpec.groovy @@ -4,7 +4,11 @@ import com.dtolabs.rundeck.core.common.Framework import com.dtolabs.rundeck.core.common.INodeSet import com.dtolabs.rundeck.core.resources.format.ResourceFormatParser import com.dtolabs.rundeck.core.resources.format.ResourceFormatParserService +import com.dtolabs.rundeck.core.storage.keys.KeyStorageTree +import org.rundeck.app.spi.Services +import org.rundeck.storage.api.Resource import spock.lang.Specification +import com.dtolabs.rundeck.core.storage.ResourceMeta /** * Created by luistoledo on 12/22/17. @@ -160,6 +164,108 @@ class GitResourceModelSpec extends Specification{ 'resources' |'resources.json' | 'json' } + def "retrieve resource success using password authentication from key storage"() { + given: + + def nodeSet = Mock(INodeSet) + def framework = getFramework(nodeSet) + + String path = "resources" + String fileName = "resources.xml" + String format = "xml" + + File folder = new File(path) + if(!folder.exists()){ + folder.mkdir() + } + + Properties configuration = [ + gitBaseDirectory:path, + gitFormatFile:format, + gitFile:fileName, + gitPasswordPathStorage:"keys/git/password", + ] + + def gitManager = Mock(GitManager) + + def inputStream = GroovyMock(InputStream) + KeyStorageTree keyStorageTree = Mock(KeyStorageTree){ + 1 * getResource(_) >> Mock(Resource) { + 1 * getContents() >> Mock(ResourceMeta) { + writeContent(_) >> { args -> + args[0].write('password'.bytes) + return 6L + } + } + } + } + + Services services = Mock(Services){ + 1 * getService(KeyStorageTree) >> keyStorageTree + } + + when: + + def resource = new GitResourceModel(services,configuration,framework) + resource.setGitManager(gitManager) + + def result = resource.getNodes() + + then: + 1 * gitManager.getFile(path) >> inputStream + result == nodeSet + } + + def "retrieve resource success using SSH key authentication from key storage"() { + given: + + def nodeSet = Mock(INodeSet) + def framework = getFramework(nodeSet) + + String path = "resources" + String fileName = "resources.xml" + String format = "xml" + File folder = new File(path) + if(!folder.exists()){ + folder.mkdir() + } + + Properties configuration = [ + gitBaseDirectory:path, + gitFormatFile:format, + gitFile:fileName, + gitKeyPathStorage:"keys/git/ssh-key", + ] + + def gitManager = Mock(GitManager) + + def inputStream = GroovyMock(InputStream) + KeyStorageTree keyStorageTree = Mock(KeyStorageTree){ + 1 * getResource(_) >> Mock(Resource) { + 1 * getContents() >> Mock(ResourceMeta) { + writeContent(_) >> { args -> + args[0].write('-----BEGIN RSA PRIVATE KEY-----\ntest key content\n-----END RSA PRIVATE KEY-----'.bytes) + return 65L + } + } + } + } + + Services services = Mock(Services){ + 1 * getService(KeyStorageTree) >> keyStorageTree + } + + when: + + def resource = new GitResourceModel(services,configuration,framework) + resource.setGitManager(gitManager) + + def result = resource.getNodes() + + then: + 1 * gitManager.getFile(path) >> inputStream + result == nodeSet + } private Framework getFramework(INodeSet nodeSet){ def resourceFormatParser = Mock(ResourceFormatParser){