Skip to content

Commit

Permalink
Add BasicSSHUserPrivateKey credential convertor
Browse files Browse the repository at this point in the history
Support SSH cred with or without passphrase
  • Loading branch information
fydrah committed Aug 31, 2018
1 parent afb1e24 commit 5b0ad19
Show file tree
Hide file tree
Showing 18 changed files with 887 additions and 2 deletions.
14 changes: 13 additions & 1 deletion docs/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permalink: /examples/

Credentials are added and updated by adding/updating them as secrets to Kubernetes.
The format of the Secret is different depending on the type of credential you wish to expose, but will all have several things in common:
- the label `"jenkins.io/credentials-type"` with a type that is known to the plugin (e.g. `certificate`, `secretFile`, `secretText`, `usernamePassword`)
- the label `"jenkins.io/credentials-type"` with a type that is known to the plugin (e.g. `certificate`, `secretFile`, `secretText`, `usernamePassword`, `basicSSHUserPrivateKey`)
- an annotation for the credential description: `"jenkins.io/credentials-description" : "certificate credential from Kubernetes"`

To add or update a Credential just execute the command `kubectl apply -f <nameOfFile.yaml>`
Expand Down Expand Up @@ -44,6 +44,18 @@ The UserName password credentials are probably the most commonly uses.
{% include_relative certificate.yaml %}
{% endhighlight %}

## Basic SSH Private Key

Without passphrase:
{% highlight ruby linenos %}
{% include_relative basic-ssh-username-private-key.yaml %}
{% endhighlight %}

With passphrase:
{% highlight ruby linenos %}
{% include_relative basic-ssh-username-private-key-passphrase.yaml %}
{% endhighlight %}


# Custom field mapping

Expand Down
47 changes: 47 additions & 0 deletions docs/examples/basic-ssh-username-private-key-passphrase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
apiVersion: v1
kind: Secret
metadata:
# this is the jenkins id.
name: "jenkins-key"
labels:
# so we know what type it is.
"jenkins.io/credentials-type": "basicSSHUserPrivateKey"
annotations: {
# description - can not be a label as spaces are not allowed
"jenkins.io/credentials-description" : "basic user private key credential from Kubernetes"
}
type: Opaque
data:
# base64 encoded username
username: "amVua2lucw==" # jenkins
# base64 encoded passphrase
passphrase: "bXlwYXNzcGhyYXNl" # mypassphrase
# base64 encoded key
# -----BEGIN RSA PRIVATE KEY-----
# MIIEowIBAAKCAQEAngWMYnda9vD2utvbAdgCOLVNanA/MW50er5ROW21it/eph1u
# 6RCuZ0CiuYUE5Eb8kOOQP7MTL3Ixyv9GW6hmMZwjyvcCamKj7cYuEHBYkn0X2Jgw
# syPGUWZwITgSxgb/VfjRKbAtUdvXNFjHxknUlaVd+G6gQpN5Lv3//O/EglmVqf1d
# CM2xAy9Ixk9roMSmBpgwC7lCsi1W9IGdLrjLAC96BrJkHX1EDQDdB8tWg8qLjZfr
# L1ioddG/NDH8lOUetWX9SB5WF4xi/oBRNvSCwmBAa8v2DvhS/TEwcWAsReclRCNW
# 5eGAqhbb0Kl8E0hYJdFlEKYjQH3y5cZtqMAiuwIDAQABAoIBAGQK2TThoYpjRaFJ
# XZ8ONWHXjpqLU8akykOHR/8WsO+qCdibG8OcFv4xkpPnXhBzzKSiHYnmgofwQQvm
# j5GpzIEt/A8cUMAvkN8RL8qihcDAR5+Nwo83X+/a7bRqPqB2f6LbMvi0nAyOJPH0
# Hw4vYdIX7qVAzF855GfW0QE+fueSdtgWviJM8gZHdhCqe/zqYm016zNaavap530r
# tJ/+vhUW8WYqJqBW8+58laW5vTBusNsVjeL40yJF8X/XQQcdZ4XmthNcegx79oim
# j9ELzX0ttchiwAe/trLxTkdWb4rEFz+U50iAOMUdS8T0brb5bxhqNM/ByiqQ28W9
# 2NJCVEkCgYEA0phCE9iKVWNZnvWX6+fHgr2NO2ShPexPeRfFxr0ugXGTQvyT0HnM
# /Q//V+LduPMX8b2AsOzI0rQh+4bjohOZvKmGKiuPv3eSvqpi/r6208ZVTBjjFvBO
# UQhMbPUyR6vO1ryFDwBMwMqQ06ldkXArhB+SG0dYnOKb/6g0nO2BVFUCgYEAwBeH
# HGNGuxwum63UAaqyX6lRSpGGm6XSCBhzvHUPnVphgq7nnZOGl0z3U49jreCvuuEc
# fA9YqxJjzoZy5870KOXY2kltlq/U/4Lrb0k75ag6ZVbi0oemACN6KCHtE+Zm2dac
# rW8oKWpRTbsvMOYUvSjF0u8BCrestpRUF977Ks8CgYEAicbLFCjK9+ozq+eJKPFO
# eZ6BU6YWR2je5Z5D6i3CyzT+3whXvECzd6yLpXfrDyEbPTB5jUacbB0lTmWFb3fb
# UK6n89bkCKO2Ab9/XKJxAkPzcgGmME+vLRx8w5v29STWAW78rj/H9ymPbqqTaJ82
# GQ5+jBI1Sw6GeNAW+8P2pLECgYAs/dXBimcosCMih4ZelZKN4WSO6KL0ldQp3UBO
# ZcSwgFjSeRD60XD2wyoywiUAtt2yEcPQMu/7saT63HbRYKHDaoJuLkCiyLBE4G8w
# c6C527tBvSYHVYpGAgk8mSWkQZTZdPDhlmV7vdEpOayF8X3uCDy9eQlvbzHe2cMQ
# jEOb9QKBgG3jSxGfqN/sD8W9BhpVrybCXh2RvhxOBJAFx58wSWTkRcYSwpdyvm7x
# wlMtcEdQgaSBeuBU3HPUdYE07bQNAlYO0p9MQnsLHzd2V9yiCX1Sq5iB6dQpHxyi
# sDZLY2Mym1nUJWfE47GAcxFZtrVh9ojKcmgiHo8qPTkWjFGY7xe/
# -----END RSA PRIVATE KEY-----
privateKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbmdXTVluZGE5dkQydXR2YkFkZ0NPTFZOYW5BL01XNTBlcjVST1cyMWl0L2VwaDF1CjZSQ3VaMENpdVlVRTVFYjhrT09RUDdNVEwzSXh5djlHVzZobU1ad2p5dmNDYW1LajdjWXVFSEJZa24wWDJKZ3cKc3lQR1VXWndJVGdTeGdiL1ZmalJLYkF0VWR2WE5Gakh4a25VbGFWZCtHNmdRcE41THYzLy9PL0VnbG1WcWYxZApDTTJ4QXk5SXhrOXJvTVNtQnBnd0M3bENzaTFXOUlHZExyakxBQzk2QnJKa0hYMUVEUURkQjh0V2c4cUxqWmZyCkwxaW9kZEcvTkRIOGxPVWV0V1g5U0I1V0Y0eGkvb0JSTnZTQ3dtQkFhOHYyRHZoUy9URXdjV0FzUmVjbFJDTlcKNWVHQXFoYmIwS2w4RTBoWUpkRmxFS1lqUUgzeTVjWnRxTUFpdXdJREFRQUJBb0lCQUdRSzJUVGhvWXBqUmFGSgpYWjhPTldIWGpwcUxVOGFreWtPSFIvOFdzTytxQ2RpYkc4T2NGdjR4a3BQblhoQnp6S1NpSFlubWdvZndRUXZtCmo1R3B6SUV0L0E4Y1VNQXZrTjhSTDhxaWhjREFSNStOd284M1grL2E3YlJxUHFCMmY2TGJNdmkwbkF5T0pQSDAKSHc0dllkSVg3cVZBekY4NTVHZlcwUUUrZnVlU2R0Z1d2aUpNOGdaSGRoQ3FlL3pxWW0wMTZ6TmFhdmFwNTMwcgp0Si8rdmhVVzhXWXFKcUJXOCs1OGxhVzV2VEJ1c05zVmplTDQweUpGOFgvWFFRY2RaNFhtdGhOY2VneDc5b2ltCmo5RUx6WDB0dGNoaXdBZS90ckx4VGtkV2I0ckVGeitVNTBpQU9NVWRTOFQwYnJiNWJ4aHFOTS9CeWlxUTI4VzkKMk5KQ1ZFa0NnWUVBMHBoQ0U5aUtWV05abnZXWDYrZkhncjJOTzJTaFBleFBlUmZGeHIwdWdYR1RRdnlUMEhuTQovUS8vVitMZHVQTVg4YjJBc096STByUWgrNGJqb2hPWnZLbUdLaXVQdjNlU3ZxcGkvcjYyMDhaVlRCampGdkJPClVRaE1iUFV5UjZ2TzFyeUZEd0JNd01xUTA2bGRrWEFyaEIrU0cwZFluT0tiLzZnMG5PMkJWRlVDZ1lFQXdCZUgKSEdOR3V4d3VtNjNVQWFxeVg2bFJTcEdHbTZYU0NCaHp2SFVQblZwaGdxN25uWk9HbDB6M1U0OWpyZUN2dXVFYwpmQTlZcXhKanpvWnk1ODcwS09YWTJrbHRscS9VLzRMcmIwazc1YWc2WlZiaTBvZW1BQ042S0NIdEUrWm0yZGFjCnJXOG9LV3BSVGJzdk1PWVV2U2pGMHU4QkNyZXN0cFJVRjk3N0tzOENnWUVBaWNiTEZDaks5K296cStlSktQRk8KZVo2QlU2WVdSMmplNVo1RDZpM0N5elQrM3doWHZFQ3pkNnlMcFhmckR5RWJQVEI1alVhY2JCMGxUbVdGYjNmYgpVSzZuODlia0NLTzJBYjkvWEtKeEFrUHpjZ0dtTUUrdkxSeDh3NXYyOVNUV0FXNzhyai9IOXltUGJxcVRhSjgyCkdRNStqQkkxU3c2R2VOQVcrOFAycExFQ2dZQXMvZFhCaW1jb3NDTWloNFplbFpLTjRXU082S0wwbGRRcDNVQk8KWmNTd2dGalNlUkQ2MFhEMnd5b3l3aVVBdHQyeUVjUFFNdS83c2FUNjNIYlJZS0hEYW9KdUxrQ2l5TEJFNEc4dwpjNkM1Mjd0QnZTWUhWWXBHQWdrOG1TV2tRWlRaZFBEaGxtVjd2ZEVwT2F5RjhYM3VDRHk5ZVFsdmJ6SGUyY01RCmpFT2I5UUtCZ0czalN4R2ZxTi9zRDhXOUJocFZyeWJDWGgyUnZoeE9CSkFGeDU4d1NXVGtSY1lTd3BkeXZtN3gKd2xNdGNFZFFnYVNCZXVCVTNIUFVkWUUwN2JRTkFsWU8wcDlNUW5zTEh6ZDJWOXlpQ1gxU3E1aUI2ZFFwSHh5aQpzRFpMWTJNeW0xblVKV2ZFNDdHQWN4Rlp0clZoOW9qS2NtZ2lIbzhxUFRrV2pGR1k3eGUvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg=="
45 changes: 45 additions & 0 deletions docs/examples/basic-ssh-username-private-key.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
apiVersion: v1
kind: Secret
metadata:
# this is the jenkins id.
name: "jenkins-key"
labels:
# so we know what type it is.
"jenkins.io/credentials-type": "basicSSHUserPrivateKey"
annotations: {
# description - can not be a label as spaces are not allowed
"jenkins.io/credentials-description" : "basic user private key credential from Kubernetes"
}
type: Opaque
data:
# base64 encoded username
username: "amVua2lucw==" # jenkins
# base64 encoded key
# -----BEGIN RSA PRIVATE KEY-----
# MIIEowIBAAKCAQEAngWMYnda9vD2utvbAdgCOLVNanA/MW50er5ROW21it/eph1u
# 6RCuZ0CiuYUE5Eb8kOOQP7MTL3Ixyv9GW6hmMZwjyvcCamKj7cYuEHBYkn0X2Jgw
# syPGUWZwITgSxgb/VfjRKbAtUdvXNFjHxknUlaVd+G6gQpN5Lv3//O/EglmVqf1d
# CM2xAy9Ixk9roMSmBpgwC7lCsi1W9IGdLrjLAC96BrJkHX1EDQDdB8tWg8qLjZfr
# L1ioddG/NDH8lOUetWX9SB5WF4xi/oBRNvSCwmBAa8v2DvhS/TEwcWAsReclRCNW
# 5eGAqhbb0Kl8E0hYJdFlEKYjQH3y5cZtqMAiuwIDAQABAoIBAGQK2TThoYpjRaFJ
# XZ8ONWHXjpqLU8akykOHR/8WsO+qCdibG8OcFv4xkpPnXhBzzKSiHYnmgofwQQvm
# j5GpzIEt/A8cUMAvkN8RL8qihcDAR5+Nwo83X+/a7bRqPqB2f6LbMvi0nAyOJPH0
# Hw4vYdIX7qVAzF855GfW0QE+fueSdtgWviJM8gZHdhCqe/zqYm016zNaavap530r
# tJ/+vhUW8WYqJqBW8+58laW5vTBusNsVjeL40yJF8X/XQQcdZ4XmthNcegx79oim
# j9ELzX0ttchiwAe/trLxTkdWb4rEFz+U50iAOMUdS8T0brb5bxhqNM/ByiqQ28W9
# 2NJCVEkCgYEA0phCE9iKVWNZnvWX6+fHgr2NO2ShPexPeRfFxr0ugXGTQvyT0HnM
# /Q//V+LduPMX8b2AsOzI0rQh+4bjohOZvKmGKiuPv3eSvqpi/r6208ZVTBjjFvBO
# UQhMbPUyR6vO1ryFDwBMwMqQ06ldkXArhB+SG0dYnOKb/6g0nO2BVFUCgYEAwBeH
# HGNGuxwum63UAaqyX6lRSpGGm6XSCBhzvHUPnVphgq7nnZOGl0z3U49jreCvuuEc
# fA9YqxJjzoZy5870KOXY2kltlq/U/4Lrb0k75ag6ZVbi0oemACN6KCHtE+Zm2dac
# rW8oKWpRTbsvMOYUvSjF0u8BCrestpRUF977Ks8CgYEAicbLFCjK9+ozq+eJKPFO
# eZ6BU6YWR2je5Z5D6i3CyzT+3whXvECzd6yLpXfrDyEbPTB5jUacbB0lTmWFb3fb
# UK6n89bkCKO2Ab9/XKJxAkPzcgGmME+vLRx8w5v29STWAW78rj/H9ymPbqqTaJ82
# GQ5+jBI1Sw6GeNAW+8P2pLECgYAs/dXBimcosCMih4ZelZKN4WSO6KL0ldQp3UBO
# ZcSwgFjSeRD60XD2wyoywiUAtt2yEcPQMu/7saT63HbRYKHDaoJuLkCiyLBE4G8w
# c6C527tBvSYHVYpGAgk8mSWkQZTZdPDhlmV7vdEpOayF8X3uCDy9eQlvbzHe2cMQ
# jEOb9QKBgG3jSxGfqN/sD8W9BhpVrybCXh2RvhxOBJAFx58wSWTkRcYSwpdyvm7x
# wlMtcEdQgaSBeuBU3HPUdYE07bQNAlYO0p9MQnsLHzd2V9yiCX1Sq5iB6dQpHxyi
# sDZLY2Mym1nUJWfE47GAcxFZtrVh9ojKcmgiHo8qPTkWjFGY7xe/
# -----END RSA PRIVATE KEY-----
privateKey: "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBbmdXTVluZGE5dkQydXR2YkFkZ0NPTFZOYW5BL01XNTBlcjVST1cyMWl0L2VwaDF1CjZSQ3VaMENpdVlVRTVFYjhrT09RUDdNVEwzSXh5djlHVzZobU1ad2p5dmNDYW1LajdjWXVFSEJZa24wWDJKZ3cKc3lQR1VXWndJVGdTeGdiL1ZmalJLYkF0VWR2WE5Gakh4a25VbGFWZCtHNmdRcE41THYzLy9PL0VnbG1WcWYxZApDTTJ4QXk5SXhrOXJvTVNtQnBnd0M3bENzaTFXOUlHZExyakxBQzk2QnJKa0hYMUVEUURkQjh0V2c4cUxqWmZyCkwxaW9kZEcvTkRIOGxPVWV0V1g5U0I1V0Y0eGkvb0JSTnZTQ3dtQkFhOHYyRHZoUy9URXdjV0FzUmVjbFJDTlcKNWVHQXFoYmIwS2w4RTBoWUpkRmxFS1lqUUgzeTVjWnRxTUFpdXdJREFRQUJBb0lCQUdRSzJUVGhvWXBqUmFGSgpYWjhPTldIWGpwcUxVOGFreWtPSFIvOFdzTytxQ2RpYkc4T2NGdjR4a3BQblhoQnp6S1NpSFlubWdvZndRUXZtCmo1R3B6SUV0L0E4Y1VNQXZrTjhSTDhxaWhjREFSNStOd284M1grL2E3YlJxUHFCMmY2TGJNdmkwbkF5T0pQSDAKSHc0dllkSVg3cVZBekY4NTVHZlcwUUUrZnVlU2R0Z1d2aUpNOGdaSGRoQ3FlL3pxWW0wMTZ6TmFhdmFwNTMwcgp0Si8rdmhVVzhXWXFKcUJXOCs1OGxhVzV2VEJ1c05zVmplTDQweUpGOFgvWFFRY2RaNFhtdGhOY2VneDc5b2ltCmo5RUx6WDB0dGNoaXdBZS90ckx4VGtkV2I0ckVGeitVNTBpQU9NVWRTOFQwYnJiNWJ4aHFOTS9CeWlxUTI4VzkKMk5KQ1ZFa0NnWUVBMHBoQ0U5aUtWV05abnZXWDYrZkhncjJOTzJTaFBleFBlUmZGeHIwdWdYR1RRdnlUMEhuTQovUS8vVitMZHVQTVg4YjJBc096STByUWgrNGJqb2hPWnZLbUdLaXVQdjNlU3ZxcGkvcjYyMDhaVlRCampGdkJPClVRaE1iUFV5UjZ2TzFyeUZEd0JNd01xUTA2bGRrWEFyaEIrU0cwZFluT0tiLzZnMG5PMkJWRlVDZ1lFQXdCZUgKSEdOR3V4d3VtNjNVQWFxeVg2bFJTcEdHbTZYU0NCaHp2SFVQblZwaGdxN25uWk9HbDB6M1U0OWpyZUN2dXVFYwpmQTlZcXhKanpvWnk1ODcwS09YWTJrbHRscS9VLzRMcmIwazc1YWc2WlZiaTBvZW1BQ042S0NIdEUrWm0yZGFjCnJXOG9LV3BSVGJzdk1PWVV2U2pGMHU4QkNyZXN0cFJVRjk3N0tzOENnWUVBaWNiTEZDaks5K296cStlSktQRk8KZVo2QlU2WVdSMmplNVo1RDZpM0N5elQrM3doWHZFQ3pkNnlMcFhmckR5RWJQVEI1alVhY2JCMGxUbVdGYjNmYgpVSzZuODlia0NLTzJBYjkvWEtKeEFrUHpjZ0dtTUUrdkxSeDh3NXYyOVNUV0FXNzhyai9IOXltUGJxcVRhSjgyCkdRNStqQkkxU3c2R2VOQVcrOFAycExFQ2dZQXMvZFhCaW1jb3NDTWloNFplbFpLTjRXU082S0wwbGRRcDNVQk8KWmNTd2dGalNlUkQ2MFhEMnd5b3l3aVVBdHQyeUVjUFFNdS83c2FUNjNIYlJZS0hEYW9KdUxrQ2l5TEJFNEc4dwpjNkM1Mjd0QnZTWUhWWXBHQWdrOG1TV2tRWlRaZFBEaGxtVjd2ZEVwT2F5RjhYM3VDRHk5ZVFsdmJ6SGUyY01RCmpFT2I5UUtCZ0czalN4R2ZxTi9zRDhXOUJocFZyeWJDWGgyUnZoeE9CSkFGeDU4d1NXVGtSY1lTd3BkeXZtN3gKd2xNdGNFZFFnYVNCZXVCVTNIUFVkWUUwN2JRTkFsWU8wcDlNUW5zTEh6ZDJWOXlpQ1gxU3E1aUI2ZFFwSHh5aQpzRFpMWTJNeW0xblVKV2ZFNDdHQWN4Rlp0clZoOW9qS2NtZ2lIbzhxUFRrV2pGR1k3eGUvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg=="
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@
<artifactId>plain-credentials</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-credentials</artifactId>
<version>1.13</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>variant</artifactId>
Expand Down Expand Up @@ -149,6 +154,12 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plain-credentials</artifactId>
<optional>true</optional>
</dependency>
<!-- Basic Username PrivateKey -->
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>ssh-credentials</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Optional;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
Expand Down Expand Up @@ -171,7 +172,23 @@ public static String getNonNullSecretData(Secret s, String key, String exception
return requireNonNull(s.getData().get(mappedKey), exceptionMessage, mappedKey);
}


/**
* Get optional data for the specified key (or the mapped key if key is mapped)
*
* @param s the Secret
* @param key the key to get the data for (which may be mapped to another key).
* @param exceptionMessage the detailMessage of the exception if the data for the key (or mapped key) was not
* present.
* @return Optional data for specified key
* @throws CredentialsConvertionException if the data was not present.
*/
public static Optional<String> getOptionalSecretData(Secret s, String key, String exceptionMessage) throws CredentialsConvertionException {
String mappedKey = getKeyName(s, key);
if (s.getData().containsKey(key) || s.getData().containsKey(mappedKey)) {
return Optional.of(getNonNullSecretData(s, key, exceptionMessage));
}
return Optional.empty();
}

/**
* Get the mapping for the specified key name. Secrets can override the defaults used by the plugin by specifying an
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* The MIT License
*
* Copyright 2018 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.cloudbees.jenkins.plugins.kubernetes_credentials_provider.convertors;

import io.fabric8.kubernetes.api.model.Secret;
import java.util.Optional;
import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey;
import org.jenkinsci.plugins.variant.OptionalExtension;
import com.cloudbees.jenkins.plugins.kubernetes_credentials_provider.CredentialsConvertionException;
import com.cloudbees.jenkins.plugins.kubernetes_credentials_provider.SecretToCredentialConverter;
import com.cloudbees.jenkins.plugins.kubernetes_credentials_provider.SecretUtils;
import com.cloudbees.plugins.credentials.CredentialsScope;

/**
* SecretToCredentialConvertor that converts {@link BasicSSHUserPrivateKey}.
*/
@OptionalExtension(requirePlugins={"ssh-credentials"})
public class BasicSSHUserPrivateKeyCredentialsConvertor extends SecretToCredentialConverter {

@Override
public boolean canConvert(String type) {
return "basicSSHUserPrivateKey".equals(type);
}

@Override
public BasicSSHUserPrivateKey convert(Secret secret) throws CredentialsConvertionException {

SecretUtils.requireNonNull(secret.getData(), "basicSSHUserPrivateKey definition contains no data");

String privateKeyBase64 = SecretUtils.getNonNullSecretData(secret, "privateKey", "basicSSHUserPrivateKey credential is missing the privateKey");
String privateKey = SecretUtils.requireNonNull(SecretUtils.base64DecodeToString(privateKeyBase64), "basicSSHUserPrivateKey credential has an invalid privateKey (must be base64 encoded UTF-8)");

String usernameBase64 = SecretUtils.getNonNullSecretData(secret, "username", "basicSSHUserPrivateKey credential is missing the username");
String username = SecretUtils.requireNonNull(SecretUtils.base64DecodeToString(usernameBase64), "basicSSHUserPrivateKey credential has an invalid username (must be base64 encoded UTF-8)");

Optional<String> optPassphraseBase64 = SecretUtils.getOptionalSecretData(secret, "passphrase", "basicSSHUserPrivateKey credential: failed to retrieve passphrase, assuming private key has an empty passphrase");
String passphrase = null;

if (optPassphraseBase64.isPresent()) {
passphrase = SecretUtils.requireNonNull(SecretUtils.base64DecodeToString(optPassphraseBase64.get()), "basicSSHUserPrivateKey credential has an invalid passphrase (must be base64 encoded UTF-8)");
}

return new BasicSSHUserPrivateKey(
// Scope
CredentialsScope.GLOBAL,
// ID
SecretUtils.getCredentialId(secret),
// Username
username,
// PrivateKeySource
new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(privateKey),
// Passphrase
passphrase,
// Desc
SecretUtils.getCredentialDescription(secret)
);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Optional;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import org.junit.Test;
Expand Down Expand Up @@ -188,4 +189,33 @@ public void getNonNullSecretDataWithNoMappedData() {
assertThat(cce.getMessage(), stringContainsInOrder("oops", "mapped to", mappedName));
}
}

@Test
public void getOptionalSecretDataWithEntry() throws CredentialsConvertionException {
String key = "a-key";
String datum = "some-data";
Optional<String> optdatum = Optional.of(datum);
Secret s = new SecretBuilder().withNewMetadata().endMetadata().addToData(key, datum).build();
assertThat(SecretUtils.getOptionalSecretData(s, key, "ignored"), is(optdatum));
}

@Test
public void getOptionalSecretDataWithMappedEntry() throws CredentialsConvertionException {
String key = "a-key";
String map = "not-the-key";
String datum = "some-data";
Optional<String> optdatum = Optional.of(datum);
Secret s = new SecretBuilder().withNewMetadata().addToAnnotations("jenkins.io/credentials-keybinding-"+key, map).endMetadata().addToData(map, datum).build();
assertThat(SecretUtils.getOptionalSecretData(s, key, "ignored"), is(optdatum));
}

@Test
public void getOptionalSecretDataWithMissingKey() throws CredentialsConvertionException {
String keyexists = "a-key-that-exists";
String keydoesnotexist = "a-key-that-does-not-exist";
String datum = "some-data";
Optional<String> emptyOpt = Optional.empty();
Secret s = new SecretBuilder().withNewMetadata().endMetadata().addToData(keyexists, datum).build();
assertThat(SecretUtils.getOptionalSecretData(s, keydoesnotexist, "ignored"), is(emptyOpt));
}
}

0 comments on commit 5b0ad19

Please sign in to comment.