/* * Copyright 2014-2017 Andrew Gaul <andrew@gaul.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.gaul.s3proxy; import java.io.Console; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; import com.google.inject.Module; import org.jclouds.Constants; import org.jclouds.ContextBuilder; import org.jclouds.JcloudsVersion; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.BlobStoreContext; import org.jclouds.location.reference.LocationConstants; import org.jclouds.logging.slf4j.config.SLF4JLoggingModule; import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); private Main() { throw new AssertionError("intentionally not implemented"); } private static final class Options { @Option(name = "--properties", usage = "S3Proxy configuration (required)") private File propertiesFile; @Option(name = "--version", usage = "display version") private boolean version; } public static void main(String[] args) throws Exception { Console console = System.console(); if (console == null) { System.setErr(createLoggerErrorPrintStream()); } Options options = new Options(); CmdLineParser parser = new CmdLineParser(options); try { parser.parseArgument(args); } catch (CmdLineException cle) { usage(parser); } if (options.version) { System.err.println( Main.class.getPackage().getImplementationVersion()); System.exit(0); } else if (options.propertiesFile == null) { usage(parser); } Properties properties = new Properties(); try (InputStream is = new FileInputStream(options.propertiesFile)) { properties.load(is); } properties.putAll(System.getProperties()); String s3ProxyEndpointString = properties.getProperty( S3ProxyConstants.PROPERTY_ENDPOINT); String secureEndpoint = properties.getProperty( S3ProxyConstants.PROPERTY_SECURE_ENDPOINT); String s3ProxyAuthorizationString = properties.getProperty( S3ProxyConstants.PROPERTY_AUTHORIZATION); if ((s3ProxyEndpointString == null && secureEndpoint == null) || s3ProxyAuthorizationString == null) { System.err.println("Properties file must contain:\n" + S3ProxyConstants.PROPERTY_AUTHORIZATION + "\n" + "and one of\n" + S3ProxyConstants.PROPERTY_ENDPOINT + "\n" + S3ProxyConstants.PROPERTY_SECURE_ENDPOINT); System.exit(1); } String s3ProxyServicePath = properties.getProperty( S3ProxyConstants.PROPERTY_SERVICE_PATH); AuthenticationType s3ProxyAuthorization = AuthenticationType.fromString(s3ProxyAuthorizationString); String localIdentity = null; String localCredential = null; switch (s3ProxyAuthorization) { case AWS_V2: case AWS_V4: case AWS_V2_OR_V4: localIdentity = properties.getProperty( S3ProxyConstants.PROPERTY_IDENTITY); localCredential = properties.getProperty( S3ProxyConstants.PROPERTY_CREDENTIAL); if (localIdentity == null || localCredential == null) { System.err.println("Must specify both " + S3ProxyConstants.PROPERTY_IDENTITY + " and " + S3ProxyConstants.PROPERTY_CREDENTIAL + " when using authentication"); System.exit(1); } break; case NONE: break; default: System.err.println(S3ProxyConstants.PROPERTY_AUTHORIZATION + " invalid value, was: " + s3ProxyAuthorization); System.exit(1); } String keyStorePath = properties.getProperty( S3ProxyConstants.PROPERTY_KEYSTORE_PATH); String keyStorePassword = properties.getProperty( S3ProxyConstants.PROPERTY_KEYSTORE_PASSWORD); String virtualHost = properties.getProperty( S3ProxyConstants.PROPERTY_VIRTUAL_HOST); String v4MaxNonChunkedRequestSize = properties.getProperty( S3ProxyConstants.PROPERTY_V4_MAX_NON_CHUNKED_REQUEST_SIZE); String ignoreUnknownHeaders = properties.getProperty( S3ProxyConstants.PROPERTY_IGNORE_UNKNOWN_HEADERS); String corsAllowAll = properties.getProperty( S3ProxyConstants.PROPERTY_CORS_ALLOW_ALL); BlobStore blobStore = createBlobStore(properties); Properties altProperties = new Properties(); for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String) entry.getKey(); if (key.startsWith(S3ProxyConstants.PROPERTY_ALT_JCLOUDS_PREFIX)) { key = key.substring( S3ProxyConstants.PROPERTY_ALT_JCLOUDS_PREFIX.length()); altProperties.put(key, (String) entry.getValue()); } } String eventualConsistency = properties.getProperty( S3ProxyConstants.PROPERTY_EVENTUAL_CONSISTENCY); if ("true".equalsIgnoreCase(eventualConsistency)) { BlobStore altBlobStore = createBlobStore(altProperties); int delay = Integer.parseInt(properties.getProperty( S3ProxyConstants.PROPERTY_EVENTUAL_CONSISTENCY_DELAY, "5")); double probability = Double.parseDouble(properties.getProperty( S3ProxyConstants.PROPERTY_EVENTUAL_CONSISTENCY_PROBABILITY, "1.0")); System.err.println("Emulating eventual consistency with delay " + delay + " seconds and probability " + (probability * 100) + "%"); blobStore = EventualBlobStore.newEventualBlobStore( blobStore, altBlobStore, Executors.newScheduledThreadPool(1), delay, TimeUnit.SECONDS, probability); } String nullBlobStore = properties.getProperty( S3ProxyConstants.PROPERTY_NULL_BLOBSTORE); if ("true".equalsIgnoreCase(nullBlobStore)) { System.err.println("Using null storage backend"); blobStore = NullBlobStore.newNullBlobStore(blobStore); } S3Proxy s3Proxy; try { S3Proxy.Builder s3ProxyBuilder = S3Proxy.builder() .blobStore(blobStore); if (s3ProxyEndpointString != null) { s3ProxyBuilder.endpoint(new URI(s3ProxyEndpointString)); } if (secureEndpoint != null) { s3ProxyBuilder.secureEndpoint(new URI(secureEndpoint)); } if (localIdentity != null || localCredential != null) { s3ProxyBuilder.awsAuthentication( s3ProxyAuthorization, localIdentity, localCredential); } if (keyStorePath != null || keyStorePassword != null) { s3ProxyBuilder.keyStore(keyStorePath, keyStorePassword); } if (!Strings.isNullOrEmpty(virtualHost)) { s3ProxyBuilder.virtualHost(virtualHost); } if (v4MaxNonChunkedRequestSize != null) { s3ProxyBuilder.v4MaxNonChunkedRequestSize(Long.parseLong( v4MaxNonChunkedRequestSize)); } if (ignoreUnknownHeaders != null) { s3ProxyBuilder.ignoreUnknownHeaders(Boolean.parseBoolean( ignoreUnknownHeaders)); } if (corsAllowAll != null) { s3ProxyBuilder.corsAllowAll(Boolean.parseBoolean( corsAllowAll)); } if (s3ProxyServicePath != null) { s3ProxyBuilder.servicePath(s3ProxyServicePath); } s3Proxy = s3ProxyBuilder.build(); } catch (IllegalArgumentException | IllegalStateException e) { System.err.println(e.getMessage()); System.exit(1); throw e; } try { s3Proxy.start(); } catch (Exception e) { System.err.println(e.getMessage()); System.exit(1); } } private static PrintStream createLoggerErrorPrintStream() { return new PrintStream(System.err) { private final StringBuilder builder = new StringBuilder(); @Override public void print(final String string) { logger.error(string); } @Override public void write(byte[] buf, int off, int len) { for (int i = off; i < len; ++i) { char ch = (char) buf[i]; if (ch == '\n') { if (builder.length() != 0) { print(builder.toString()); builder.setLength(0); } } else { builder.append(ch); } } } }; } private static BlobStore createBlobStore(Properties properties) throws IOException { String provider = properties.getProperty(Constants.PROPERTY_PROVIDER); String identity = properties.getProperty(Constants.PROPERTY_IDENTITY); String credential = properties.getProperty( Constants.PROPERTY_CREDENTIAL); String endpoint = properties.getProperty(Constants.PROPERTY_ENDPOINT); String region = properties.getProperty( LocationConstants.PROPERTY_REGION); if (provider.equals("filesystem") || provider.equals("transient")) { // local blobstores do not require credentials identity = Strings.nullToEmpty(identity); credential = Strings.nullToEmpty(credential); } else if (provider.equals("google-cloud-storage")) { File credentialFile = new File(credential); if (credentialFile.exists()) { credential = Files.toString(credentialFile, StandardCharsets.UTF_8); } properties.remove(Constants.PROPERTY_CREDENTIAL); } if (provider == null || identity == null || credential == null) { System.err.println("Properties file must contain:\n" + Constants.PROPERTY_PROVIDER + "\n" + Constants.PROPERTY_IDENTITY + "\n" + Constants.PROPERTY_CREDENTIAL + "\n"); } properties.setProperty(Constants.PROPERTY_USER_AGENT, String.format("s3proxy/%s jclouds/%s java/%s", Main.class.getPackage().getImplementationVersion(), JcloudsVersion.get(), System.getProperty("java.version"))); ContextBuilder builder = ContextBuilder .newBuilder(provider) .credentials(identity, credential) .modules(ImmutableList.<Module>of(new SLF4JLoggingModule())) .overrides(properties); if (endpoint != null) { builder = builder.endpoint(endpoint); } BlobStoreContext context = builder.build(BlobStoreContext.class); BlobStore blobStore = context.getBlobStore(); if (context instanceof RegionScopedBlobStoreContext && region != null) { blobStore = ((RegionScopedBlobStoreContext) context) .getBlobStore(region); } return blobStore; } private static void usage(CmdLineParser parser) { System.err.println("Usage: s3proxy [options...]"); parser.printUsage(System.err); System.exit(1); } }