![JAR search and dependency download from the Maven repository](/logo.png)
com.oneops.infoblox.InfobloxClient Maven / Gradle / Ivy
package com.oneops.infoblox;
import static com.oneops.infoblox.util.IPAddrs.requireIPv4;
import static com.oneops.infoblox.util.IPAddrs.requireIPv6;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.auto.value.AutoValue;
import com.oneops.infoblox.curl.CurlLoggingInterceptor;
import com.oneops.infoblox.model.Error;
import com.oneops.infoblox.model.JsonAdapterFactory;
import com.oneops.infoblox.model.Redacted;
import com.oneops.infoblox.model.SearchModifier;
import com.oneops.infoblox.model.a.ARec;
import com.oneops.infoblox.model.aaaa.AAAA;
import com.oneops.infoblox.model.cname.CNAME;
import com.oneops.infoblox.model.host.Host;
import com.oneops.infoblox.model.host.HostIPv4Req;
import com.oneops.infoblox.model.host.HostReq;
import com.oneops.infoblox.model.zone.ZoneAuth;
import com.squareup.moshi.Moshi;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.ConnectionSpec;
import okhttp3.Credentials;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Converter;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.moshi.MoshiConverterFactory;
/**
* A Client for interacting with Infoblox Appliance (IBA) NIOS over WAPI. This client implements the
* subset of Infoblox API.
*
* @author Suresh G
*/
@AutoValue
public abstract class InfobloxClient {
private Logger log = Logger.getLogger(getClass().getSimpleName());
/** IBA IP address of management interface */
public abstract String endPoint();
/**
* IBA WAPI version. Browse to WapiDoc to see the
* current wapi version of Infoblox appliance. Defaults to 2.5
*/
public abstract String wapiVersion();
/** IBA user name */
@Redacted
public abstract String userName();
/** IBA user password */
@Redacted
public abstract String password();
/** IBA default view. Defaults to 'default`. */
public abstract String dnsView();
/** Checks if TLS certificate validation is enabled for communicating with Infoblox. */
public abstract boolean tlsVerify();
/** Enable http curl logging for debugging. */
public abstract boolean debug();
/** IBA WAPI connection/read/write timeout. */
public abstract int timeout();
private Infoblox infoblox;
private Converter errResConverter;
/**
* Initializes the TLS retrofit client.
*
* @throws GeneralSecurityException if any error initializing the TLS context.
*/
private void init() throws GeneralSecurityException {
log.info("Initializing " + toString());
Moshi moshi = new Moshi.Builder().add(JsonAdapterFactory.create()).build();
TrustManager[] trustManagers = getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustManagers, new SecureRandom());
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
String basicCreds = Credentials.basic(userName(), password());
OkHttpClient.Builder okBuilder =
new OkHttpClient()
.newBuilder()
.sslSocketFactory(socketFactory, (X509TrustManager) trustManagers[0])
.connectionSpecs(singletonList(ConnectionSpec.MODERN_TLS))
.followSslRedirects(false)
.retryOnConnectionFailure(false)
.connectTimeout(timeout(), SECONDS)
.readTimeout(timeout(), SECONDS)
.writeTimeout(timeout(), SECONDS)
.addInterceptor(
chain -> {
HttpUrl origUrl = chain.request().url();
HttpUrl url =
origUrl
.newBuilder()
// .addQueryParameter("_max_results","1")
.addQueryParameter("_return_as_object", "1")
.build();
Request req =
chain
.request()
.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", basicCreds)
.url(url)
.build();
return chain.proceed(req);
});
if (!tlsVerify()) {
okBuilder.hostnameVerifier((host, session) -> true);
}
if (debug()) {
CurlLoggingInterceptor logIntcp = new CurlLoggingInterceptor(s -> log.info(s));
okBuilder.addNetworkInterceptor(logIntcp);
}
OkHttpClient okHttp = okBuilder.build();
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl(getBaseUrl())
.client(okHttp)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build();
infoblox = retrofit.create(Infoblox.class);
errResConverter = retrofit.responseBodyConverter(Error.class, new Annotation[0]);
}
/**
* Returns the trust-store manager. It's uses jdk trust-store if {@link #tlsVerify()} is enabled
* and can customize using the following environment variables.
*
* - javax.net.ssl.trustStore - Default trust-store , - javax.net.ssl.trustStoreType - Default
* trust-store type, - javax.net.ssl.trustStorePassword - Default trust-store password
*
*
If the {@link #tlsVerify()} is disabled, it trusts all certs using a custom trust manager.
*
* @return trust managers.
* @throws GeneralSecurityException if any error initializing trust store.
*/
private TrustManager[] getTrustManagers() throws GeneralSecurityException {
final TrustManager[] trustMgrs;
if (tlsVerify()) {
log.info("Using JDK trust-store for TLS check.");
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null); // Uses JDK trust-store.
trustMgrs = trustManagerFactory.getTrustManagers();
} else {
log.info("Skipping TLS certs verification.");
trustMgrs =
new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] chain, String authType) {}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[] {};
}
}
};
}
return trustMgrs;
}
/**
* Helper method to handle {@link Call} object and return the execution result(s). The error
* handling is done as per the response content-type.
*
* @see WAPI error-handling
*/
private T exec(Call call) throws IOException {
Response res = call.execute();
if (res.isSuccessful()) {
return res.body();
} else {
Error err;
String contentType = res.headers().get("Content-Type");
if (contentType != null && "application/json".equalsIgnoreCase(contentType)) {
err = errResConverter.convert(requireNonNull(res.errorBody()));
} else {
err = Error.create("Request failed, " + res.message(), res.code());
}
throw err.cause();
}
}
/**
* Returns infoblox WAPI base url for given version.
*
* @return WAPI base url.
*/
private String getBaseUrl() {
StringBuilder buf = new StringBuilder();
if (!endPoint().toLowerCase().startsWith("http")) {
buf.append("https://");
}
return buf.append(endPoint()).append("/wapi/v").append(wapiVersion()).append("/").toString();
}
////// Auth Zone Record //////
/**
* Fetch all Authoritative Zones.
*
* @return list of {@link ZoneAuth}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getAuthZones() throws IOException {
return exec(infoblox.queryAuthZones()).result();
}
/**
* Fetch all authoritative zones for the given domain name and search option.
*
* @param domainName fqdn.
* @param modifier {@link SearchModifier}
* @return list of {@link ZoneAuth}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getAuthZones(String domainName, SearchModifier modifier)
throws IOException {
requireNonNull(domainName, "Domain name is null");
Map options = new HashMap<>(1);
options.put("fqdn" + modifier.getValue(), domainName);
return exec(infoblox.queryAuthZone(options)).result();
}
/**
* Search all authoritative zones for the given domain name.
*
* @param domainName fqdn.
* @return list of {@link ZoneAuth}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getAuthZones(String domainName) throws IOException {
return getAuthZones(domainName, SearchModifier.NONE);
}
////// Host Record //////
/**
* Get host information for the given domain name and search option.
*
* @param domainName fqdn
* @return list of matching {@link Host}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getHostRec(String domainName, SearchModifier modifier) throws IOException {
requireNonNull(domainName, "Domain name is null");
Map options = new HashMap<>(1);
options.put("name" + modifier.getValue(), domainName);
return exec(infoblox.queryHostRec(options)).result();
}
/**
* Get host information for the given domain name.
*
* @param domainName fqdn
* @return list of matching {@link Host}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getHostRec(String domainName) throws IOException {
return getHostRec(domainName, SearchModifier.NONE);
}
/**
* Creates IBA host record.
*
* @param domainName hostname in FQDN
* @param ipv4Addrs IPv4 address(s)
* @return {@link Host} containing IPv4 addresses for the hostname.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public Host createHostRec(String domainName, List ipv4Addrs) throws IOException {
List reqList =
ipv4Addrs.stream().map(HostIPv4Req::create).collect(Collectors.toList());
HostReq hostReq = HostReq.create(domainName, reqList);
return exec(infoblox.createHostRec(hostReq)).result();
}
/**
* Deletes IBA host record with given domain name.
*
* @param domainName fqdn for the host record.
* @return list of deleted host references.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List deleteHostRec(String domainName) throws IOException {
return getHostRec(domainName)
.stream()
.map(Host::ref)
.filter(ref -> ref.contains(domainName))
.map(
ref -> {
try {
return exec(infoblox.deleteRef(ref)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error deleting Host record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
////// A Record //////
/**
* Get address records (A Record) for the given domain name and search option.
*
* @param domainName fqdn
* @return list of matching {@link ARec}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getARec(String domainName, SearchModifier modifier) throws IOException {
requireNonNull(domainName, "Domain name is null");
Map options = new HashMap<>(1);
options.put("name" + modifier.getValue(), domainName);
return exec(infoblox.queryARec(options)).result();
}
/**
* Get address records (A Record) for the given domain name.
*
* @param domainName fqdn
* @return list of matching {@link ARec}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getARec(String domainName) throws IOException {
return getARec(domainName, SearchModifier.NONE);
}
/**
* Creates an address record (A Record)
*
* @param domainName FQDN
* @param ipv4Address IPv4 address
* @return {@link ARec} address record.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public ARec createARec(String domainName, String ipv4Address) throws IOException {
requireIPv4(ipv4Address);
Map req = new HashMap<>(2);
req.put("name", domainName);
req.put("ipv4addr", ipv4Address);
return exec(infoblox.createARec(req)).result();
}
/**
* Deletes address record with given domain name.
*
* @param domainName fqdn for the A record.
* @return list of A record obj references deleted.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List deleteARec(String domainName) throws IOException {
return getARec(domainName)
.stream()
.map(ARec::ref)
.filter(ref -> ref.contains(domainName))
.map(
ref -> {
try {
return exec(infoblox.deleteRef(ref)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error deleting A record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
/**
* Modify the domain name of A record with given name.
*
* @param domainName fqdn for the A record.
* @param newDomainName new fqdn.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List modifyARec(String domainName, String newDomainName) throws IOException {
return getARec(domainName)
.stream()
.map(ARec::ref)
.filter(ref -> ref.contains(domainName))
.map(
ref -> {
Map req = new HashMap<>(1);
req.put("name", newDomainName);
try {
return exec(infoblox.modifyARec(ref, req)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error modifying A record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
////// AAAA Record //////
/**
* Get IPv6 address records (AAAA) for the given domain name and search option.
*
* @param domainName fqdn
* @return list of matching {@link AAAA}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getAAAARec(String domainName, SearchModifier modifier) throws IOException {
requireNonNull(domainName, "Domain name is null");
Map options = new HashMap<>(1);
options.put("name" + modifier.getValue(), domainName);
return exec(infoblox.queryAAAARec(options)).result();
}
/**
* Get IPv6 address records (AAAA) for the given domain name.
*
* @param domainName fqdn
* @return list of matching {@link AAAA}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getAAAARec(String domainName) throws IOException {
return getAAAARec(domainName, SearchModifier.NONE);
}
/**
* Creates an IPv6 address record (AAAA Record)
*
* @param domainName FQDN
* @param ipv6Address IPv6 address
* @return {@link AAAA} address record.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public AAAA createAAAARec(String domainName, String ipv6Address) throws IOException {
requireIPv6(ipv6Address);
Map req = new HashMap<>(2);
req.put("name", domainName);
req.put("ipv6addr", ipv6Address);
return exec(infoblox.createAAAARec(req)).result();
}
/**
* Deletes IPv6 address record with given domain name.
*
* @param domainName fqdn for the AAAA record.
* @return list of A record obj references deleted.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List deleteAAAARec(String domainName) throws IOException {
return getAAAARec(domainName)
.stream()
.map(AAAA::ref)
.filter(ref -> ref.contains(domainName))
.map(
ref -> {
try {
return exec(infoblox.deleteRef(ref)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error deleting AAAA record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
/**
* Modify the domain name of AAAA record with given name.
*
* @param domainName fqdn for the AAAA record.
* @param newDomainName new fqdn.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List modifyAAAARec(String domainName, String newDomainName) throws IOException {
return getAAAARec(domainName)
.stream()
.map(AAAA::ref)
.filter(ref -> ref.contains(domainName))
.map(
ref -> {
Map req = new HashMap<>(1);
req.put("name", newDomainName);
try {
return exec(infoblox.modifyAAAARec(ref, req)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error modifying AAAA record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
////// CNAME Record //////
/**
* Get canonical records (CNAME) for the given alias name and search option.
*
* @param aliasName fqdn
* @return list of matching {@link CNAME}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getCNameRec(String aliasName, SearchModifier modifier) throws IOException {
requireNonNull(aliasName, "Domain name is null");
Map options = new HashMap<>(1);
options.put("name" + modifier.getValue(), aliasName);
return exec(infoblox.queryCNAMERec(options)).result();
}
/**
* Get canonical records (CNAME) for the given alias name.
*
* @param aliasName fqdn
* @return list of matching {@link CNAME}
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List getCNameRec(String aliasName) throws IOException {
return getCNameRec(aliasName, SearchModifier.NONE);
}
/**
* Creates a canonical record (CNAME Record). CNAME records must always point to another domain
* name, never directly to an IP address.
*
* @param aliasName alias domain name
* @param canonicalName Canonical (true/actual) domain name.
* @return {@link CNAME} record.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public CNAME createCNameRec(String aliasName, String canonicalName) throws IOException {
Map req = new HashMap<>(2);
req.put("name", aliasName);
req.put("canonical", canonicalName);
return exec(infoblox.createCNAMERec(req)).result();
}
/**
* Deletes canonical record with given alias name.
*
* @param aliasName alias name to be deleted.
* @return list of CNAME record obj references deleted.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List deleteCNameRec(String aliasName) throws IOException {
return getCNameRec(aliasName)
.stream()
.map(CNAME::ref)
.filter(ref -> ref.contains(aliasName))
.map(
ref -> {
try {
return exec(infoblox.deleteRef(ref)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error deleting CNAME record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
/**
* Modify alias name of the CNAME record with new name.
*
* @param aliasName alias name.
* @param newAliasName new fqdn.
* @throws IOException if a problem occurred talking to the infoblox.
*/
public List modifyCNameRec(String aliasName, String newAliasName) throws IOException {
return getCNameRec(aliasName)
.stream()
.map(CNAME::ref)
.filter(ref -> ref.contains(aliasName))
.map(
ref -> {
Map req = new HashMap<>(1);
req.put("name", newAliasName);
try {
return exec(infoblox.modifyCNAMERec(ref, req)).result();
} catch (IOException ioe) {
throw new IllegalStateException("Error modifying A record ref: " + ref, ioe);
}
})
.collect(Collectors.toList());
}
////// TXT Record //////
////// PTR Record //////
////// SRV Record //////
/**
* Returns the builder for {@link InfobloxClient} with default values for un-initialized optional
* fields.
*
* @return Builder
*/
public static Builder builder() {
return new AutoValue_InfobloxClient.Builder()
.tlsVerify(true)
.timeout(10)
.wapiVersion("2.5")
.dnsView("default")
.debug(false);
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder endPoint(String endPoint);
public abstract Builder wapiVersion(String wapiVersion);
public abstract Builder userName(String userName);
public abstract Builder password(String password);
public abstract Builder dnsView(String dnsView);
public abstract Builder tlsVerify(boolean tlsVerify);
public abstract Builder timeout(int timeout);
public abstract Builder debug(boolean debug);
abstract InfobloxClient autoBuild();
/**
* Build and initialize Infoblox client.
*
* @return client.
*/
public InfobloxClient build() {
InfobloxClient client = autoBuild();
try {
client.init();
} catch (GeneralSecurityException ex) {
throw new IllegalArgumentException("InfobloxClient init failed.", ex);
}
return client;
}
}
}