package org.csanchez.jenkins.plugins.kubernetes; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import hudson.Extension; import hudson.util.Secret; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64InputStream; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ProtocolException; import org.apache.http.client.RedirectStrategy; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.protocol.HttpContext; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.TrustStrategy; import org.kohsuke.stapler.DataBoundConstructor; import javax.net.ssl.HostnameVerifier; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** * @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a> */ public class OpenShiftBearerTokenCredentialImpl extends UsernamePasswordCredentialsImpl implements TokenProducer { private static final long serialVersionUID = 6031616605797622926L; private transient AtomicReference<Token> token = new AtomicReference<Token>(); @DataBoundConstructor public OpenShiftBearerTokenCredentialImpl(CredentialsScope scope, String id, String description, String username, String password) { super(scope, id, description, username, password); } private Object readResolve() { token = new AtomicReference<Token>(); return this; } @Override public String getToken(String serviceAddress, String caCertData, boolean skipTlsVerify) throws IOException { Token t = this.token.get(); if (t == null || System.currentTimeMillis() > t.expire) { t = refreshToken(serviceAddress, caCertData, skipTlsVerify); } return t.value; } private synchronized Token refreshToken(String serviceAddress, String caCertData, boolean skipTlsVerify) throws IOException { URI uri = null; try { uri = new URI(serviceAddress); } catch (URISyntaxException e) { throw new IOException("Invalid server URL "+serviceAddress, e); } final HttpClientBuilder builder = HttpClients.custom() .setRedirectStrategy(NO_REDIRECT); if (skipTlsVerify || caCertData != null) { final SSLContextBuilder sslBuilder = new SSLContextBuilder(); HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier(); try { if (skipTlsVerify) { sslBuilder.loadTrustMaterial(null, ALWAYS); hostnameVerifier = NoopHostnameVerifier.INSTANCE; } else if (caCertData != null) { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); ks.load(null); CertificateFactory f = CertificateFactory.getInstance("X509"); X509Certificate cert = (X509Certificate) f.generateCertificate(new Base64InputStream( new ByteArrayInputStream(caCertData.getBytes(StandardCharsets.UTF_8)))); ks.setCertificateEntry(uri.getHost(), cert); sslBuilder.loadTrustMaterial(ks, null); } builder.setSSLSocketFactory(new SSLConnectionSocketFactory(sslBuilder.build(), hostnameVerifier)); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } HttpGet authorize = new HttpGet(serviceAddress + "/oauth/authorize?client_id=openshift-challenging-client&response_type=token"); authorize.setHeader("Authorization", "Basic "+ Base64.encodeBase64String( (getUsername()+':'+Secret.toString(getPassword())) .getBytes(StandardCharsets.UTF_8))); final CloseableHttpResponse response = builder.build().execute(authorize); if (response.getStatusLine().getStatusCode() != 302) { throw new IOException("Failed to get an OAuth access token " + response.getStatusLine().getStatusCode()); } String location = response.getFirstHeader("Location").getValue(); String parameters = location.substring(location.indexOf('#')+1); List<NameValuePair> pairs = URLEncodedUtils.parse(parameters, StandardCharsets.UTF_8); Token t = new Token(); for (NameValuePair pair : pairs) { if (pair.getName().equals("access_token")) { t.value = pair.getValue(); } else if (pair.getName().equals("expires_in")) { t.expire = System.currentTimeMillis() + Long.parseLong(pair.getValue())*1000 - 100; } } return t; } @Extension public static class DescriptorImpl extends BaseStandardCredentialsDescriptor { @Override public String getDisplayName() { return "OpenShift Username and Password"; } } private static class Token { String value; long expire; } private static TrustStrategy ALWAYS = new TrustStrategy() { @Override public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { return true; } }; private static RedirectStrategy NO_REDIRECT = new RedirectStrategy() { @Override public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException { return false; } @Override public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException { return null; } }; }