Skip to content

Commit

Permalink
akka#2171: add access-style property
Browse files Browse the repository at this point in the history
  • Loading branch information
laszlovandenhoek committed Sep 3, 2020
1 parent b3bb8d5 commit 639acb6
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 59 deletions.
13 changes: 12 additions & 1 deletion s3/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,22 @@ alpakka.s3 {
# AWS S3 is going to retire path-style access, thus prefer the virtual-host-style access
# https://aws.amazon.com/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story/
#
# Also, prefer to use the newer access-style property.
#
# Possible values:
# - false: use virtual-host style access
# - true: use legacy path-style access, warnings will be logged
# - force: use legacy path-style access, disable warnings
path-style-access = false
# - (empty): use `access-style`, which defaults to virtual-host style access
path-style-access = ""

# Which access style to use. Prefer to use this setting over path-style-access.
# Path-style access has been deprecated by Amazon and will not work on buckets created after September 30, 2020.
# For alternative S3 implementations (MinIO, Rados Gateway, etc.), path-style access may continue to be required.
# Possible values:
# - virtual: virtual host-style access, i.e. https://<bucket name>.s3.amazonaws.com/file/inside/bucket
# - path: path-style access, i.e. https://<region>.amazonaws.com/<bucket>/file/inside/bucket
access-style = virtual

# Custom endpoint url, used for alternate s3 implementations
# To enable virtual-host-style access with Alpakka S3 use the placeholder `{bucket}` in the URL
Expand Down
27 changes: 11 additions & 16 deletions s3/src/main/scala/akka/stream/alpakka/s3/impl/HttpRequests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model.Uri.{Authority, Query}
import akka.http.scaladsl.model.headers.{`Raw-Request-URI`, Host, RawHeader}
import akka.http.scaladsl.model.{ContentTypes, RequestEntity, _}
import akka.stream.alpakka.s3.AccessStyle.{PathAccessStyle, VirtualHostAccessStyle}
import akka.stream.alpakka.s3.{ApiVersion, S3Settings}
import akka.stream.scaladsl.Source
import akka.util.ByteString
import org.slf4j.LoggerFactory
import software.amazon.awssdk.regions.Region

import scala.collection.immutable.Seq
Expand All @@ -27,7 +27,6 @@ import scala.concurrent.{ExecutionContext, Future}
*/
@InternalApi private[impl] object HttpRequests {

private final val log = LoggerFactory.getLogger(getClass)
private final val BucketPattern = "{bucket}"

def listBucket(
Expand Down Expand Up @@ -174,31 +173,27 @@ import scala.concurrent.{ExecutionContext, Future}

@throws(classOf[IllegalUriException])
private[this] def requestAuthority(bucket: String, region: Region)(implicit conf: S3Settings): Authority = {
if (conf.pathStyleAccess && conf.pathStyleAccessWarning) {
log.warn(
"AWS S3 is going to retire path-style access (https://aws.amazon.com/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story/)"
)
}
conf.endpointUrl match {
case None if conf.pathStyleAccess =>
(conf.endpointUrl, conf.accessStyle) match {
case (None, PathAccessStyle) =>
Authority(Uri.Host(s"s3.$region.amazonaws.com"))

case None =>
case (None, VirtualHostAccessStyle) =>
Authority(Uri.Host(s"$bucket.s3.$region.amazonaws.com"))

case Some(endpointUrl) if conf.pathStyleAccess =>
case (Some(endpointUrl), PathAccessStyle) =>
Uri(endpointUrl).authority

case Some(endpointUrl) =>
case (Some(endpointUrl), VirtualHostAccessStyle) =>
Uri(endpointUrl.replace(BucketPattern, bucket)).authority
}
}

private[this] def requestUri(bucket: String, key: Option[String])(implicit conf: S3Settings): Uri = {
val basePath = if (conf.pathStyleAccess) {
Uri.Path / bucket
} else {
Uri.Path.Empty
val basePath = conf.accessStyle match {
case PathAccessStyle =>
Uri.Path / bucket
case VirtualHostAccessStyle =>
Uri.Path.Empty
}
val path = key.fold(basePath) { someKey =>
someKey.split("/", -1).foldLeft(basePath)((acc, p) => acc / p)
Expand Down
3 changes: 2 additions & 1 deletion s3/src/main/scala/akka/stream/alpakka/s3/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import java.util.{Objects, Optional}

import akka.http.scaladsl.model.{DateTime, HttpHeader, IllegalUriException, Uri}
import akka.http.scaladsl.model.headers._
import akka.stream.alpakka.s3.AccessStyle.PathAccessStyle

import scala.collection.immutable.Seq
import scala.collection.immutable
Expand Down Expand Up @@ -580,7 +581,7 @@ object BucketAndKey {
}

private[s3] def validateBucketName(bucket: String, conf: S3Settings): Unit = {
if (conf.pathStyleAccess) {
if (conf.accessStyle == PathAccessStyle) {
if (!pathStyleValid(bucket)) {
throw IllegalUriException(
"The bucket name contains sub-dir selection with `..`",
Expand Down
106 changes: 76 additions & 30 deletions s3/src/main/scala/akka/stream/alpakka/s3/settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@
package akka.stream.alpakka.s3

import java.nio.file.{Path, Paths}
import java.util.{Objects, Optional}
import java.util.concurrent.TimeUnit
import java.util.{Objects, Optional}

import scala.util.Try
import akka.actor.{ActorSystem, ClassicActorSystemProvider}
import akka.http.scaladsl.model.Uri
import software.amazon.awssdk.auth.credentials._
import software.amazon.awssdk.regions.providers._
import akka.stream.alpakka.s3.AccessStyle.{PathAccessStyle, VirtualHostAccessStyle}
import com.typesafe.config.Config
import org.slf4j.LoggerFactory
import software.amazon.awssdk.auth.credentials._
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.regions.providers._

import scala.compat.java8.OptionConverters._
import scala.concurrent.duration._
import scala.util.Try

final class Proxy private (
val host: String,
Expand Down Expand Up @@ -212,12 +214,26 @@ object ApiVersion {
def getListBucketVersion2: ListBucketVersion2 = ListBucketVersion2
}

sealed abstract class AccessStyle
object AccessStyle {
sealed abstract class PathAccessStyle extends AccessStyle
case object PathAccessStyle extends PathAccessStyle

/** Java API */
def pathAccessStyle: PathAccessStyle = PathAccessStyle

sealed abstract class VirtualHostAccessStyle extends AccessStyle
case object VirtualHostAccessStyle extends VirtualHostAccessStyle

/** Java API */
def virtualHostAccessStyle: VirtualHostAccessStyle = VirtualHostAccessStyle
}

final class S3Settings private (
val bufferType: BufferType,
val credentialsProvider: AwsCredentialsProvider,
val s3RegionProvider: AwsRegionProvider,
val pathStyleAccess: Boolean,
val pathStyleAccessWarning: Boolean,
val accessStyle: AccessStyle,
val endpointUrl: Option[String],
val listBucketApiVersion: ApiVersion,
val forwardProxy: Option[ForwardProxy],
Expand All @@ -241,7 +257,12 @@ final class S3Settings private (
def getS3RegionProvider: AwsRegionProvider = s3RegionProvider

/** Java API */
def isPathStyleAccess: Boolean = pathStyleAccess
def isPathStyleAccess: Boolean = accessStyle == PathAccessStyle

def pathStyleAccess: Boolean = accessStyle == PathAccessStyle

@deprecated("This is no longer configurable.", since = "2.0.2")
def pathStyleAccessWarning: Boolean = true

/** Java API */
def getEndpointUrl: java.util.Optional[String] = endpointUrl.asJava
Expand All @@ -252,6 +273,9 @@ final class S3Settings private (
/** Java API */
def getForwardProxy: java.util.Optional[ForwardProxy] = forwardProxy.asJava

/** Java API */
def getAccessStyle: AccessStyle = accessStyle

def withBufferType(value: BufferType): S3Settings = copy(bufferType = value)

@deprecated("Please use endpointUrl instead", since = "1.0.1")
Expand All @@ -261,12 +285,9 @@ final class S3Settings private (
copy(credentialsProvider = value)
def withS3RegionProvider(value: AwsRegionProvider): S3Settings = copy(s3RegionProvider = value)

@deprecated(
"AWS S3 is going to retire path-style access https://aws.amazon.com/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story/",
since = "2.0.0"
)
@deprecated("Please use accessStyle instead", since = "2.0.2")
def withPathStyleAccess(value: Boolean): S3Settings =
if (pathStyleAccess == value) this else copy(pathStyleAccess = value)
if (isPathStyleAccess == value) this else copy(accessStyle = if (value) PathAccessStyle else VirtualHostAccessStyle)
def withEndpointUrl(value: String): S3Settings = copy(endpointUrl = Option(value))
def withListBucketApiVersion(value: ApiVersion): S3Settings =
copy(listBucketApiVersion = value)
Expand All @@ -281,7 +302,7 @@ final class S3Settings private (
bufferType: BufferType = bufferType,
credentialsProvider: AwsCredentialsProvider = credentialsProvider,
s3RegionProvider: AwsRegionProvider = s3RegionProvider,
pathStyleAccess: Boolean = pathStyleAccess,
accessStyle: AccessStyle = accessStyle,
endpointUrl: Option[String] = endpointUrl,
listBucketApiVersion: ApiVersion = listBucketApiVersion,
forwardProxy: Option[ForwardProxy] = forwardProxy,
Expand All @@ -291,8 +312,7 @@ final class S3Settings private (
bufferType,
credentialsProvider,
s3RegionProvider,
pathStyleAccess,
pathStyleAccessWarning,
accessStyle,
endpointUrl,
listBucketApiVersion,
forwardProxy,
Expand All @@ -305,8 +325,7 @@ final class S3Settings private (
s"bufferType=$bufferType," +
s"credentialsProvider=$credentialsProvider," +
s"s3RegionProvider=$s3RegionProvider," +
s"pathStyleAccess=$pathStyleAccess," +
s"pathStyleAccessWarning=$pathStyleAccessWarning," +
s"accessStyle=$accessStyle," +
s"endpointUrl=$endpointUrl," +
s"listBucketApiVersion=$listBucketApiVersion," +
s"forwardProxy=$forwardProxy," +
Expand All @@ -318,8 +337,7 @@ final class S3Settings private (
java.util.Objects.equals(this.bufferType, that.bufferType) &&
Objects.equals(this.credentialsProvider, that.credentialsProvider) &&
Objects.equals(this.s3RegionProvider, that.s3RegionProvider) &&
Objects.equals(this.pathStyleAccess, that.pathStyleAccess) &&
Objects.equals(this.pathStyleAccessWarning, that.pathStyleAccessWarning) &&
Objects.equals(this.accessStyle, that.accessStyle) &&
Objects.equals(this.endpointUrl, that.endpointUrl) &&
Objects.equals(this.listBucketApiVersion, that.listBucketApiVersion) &&
Objects.equals(this.forwardProxy, that.forwardProxy) &&
Expand All @@ -332,8 +350,7 @@ final class S3Settings private (
bufferType,
credentialsProvider,
s3RegionProvider,
Boolean.box(pathStyleAccess),
Boolean.box(pathStyleAccessWarning),
accessStyle,
endpointUrl,
listBucketApiVersion,
forwardProxy,
Expand All @@ -342,6 +359,7 @@ final class S3Settings private (
}

object S3Settings {
private final val log = LoggerFactory.getLogger(getClass)
val ConfigPath = "alpakka.s3"

/**
Expand Down Expand Up @@ -375,16 +393,47 @@ object S3Settings {
if (c.hasPath("forward-proxy")) Some(ForwardProxy(c.getConfig("forward-proxy")))
else None

val (pathStyleAccess, pathStyleAccessWarning) =
if (c.getString("path-style-access") == "force") (true, false)
else (c.getBoolean("path-style-access"), true)
if (c.hasPath("path-style-access"))
log.warn(
"The deprecated 'path-style-access' property was used to specify access style. Please use 'access-style' instead."
)

val deprecatedPathAccessStyleSetting = Try(c.getString("path-style-access")).toOption

val accessStyle = deprecatedPathAccessStyleSetting match {
case None =>
c.getString("access-style") match {
case "virtual" => VirtualHostAccessStyle
case "path" => PathAccessStyle
case other =>
throw new IllegalArgumentException(s"'access-style' must be 'virtual' or 'path'. Got: [$other]")
}
case Some("true") | Some("force") => PathAccessStyle
case Some("false") => VirtualHostAccessStyle
case Some(other) =>
throw new IllegalArgumentException(
s"'path-style-access' must be 'false', 'true' or 'force'. Got: [$other]. Prefer using access-style instead."
)
}

val endpointUrl = if (c.hasPath("endpoint-url")) {
Option(c.getString("endpoint-url"))
} else {
None
}.orElse(maybeProxy.map(p => s"${p.scheme}://${p.host}:${p.port}"))

if (endpointUrl.isEmpty && accessStyle == PathAccessStyle)
log.warn(
s"""It appears you are attempting to use AWS S3 with path-style access.
|Amazon does not support path-style access to buckets created after September 30, 2020;
|see (https://aws.amazon.com/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story/).
|
|Enable virtual host-style access by unsetting `$ConfigPath.path-style-access`,
|and leaving `$ConfigPath.access-style` on the default `virtual`.
|
|If your S3 provider is not AWS, you need to set `$ConfigPath.endpoint-url`.""".stripMargin
)

val regionProvider = {
val regionProviderPath = "aws.region.provider"

Expand Down Expand Up @@ -450,8 +499,7 @@ object S3Settings {
bufferType,
credentialsProvider,
regionProvider,
pathStyleAccess,
pathStyleAccessWarning,
accessStyle,
endpointUrl,
apiVersion,
maybeForwardProxy,
Expand Down Expand Up @@ -479,8 +527,7 @@ object S3Settings {
bufferType,
credentialsProvider,
s3RegionProvider,
pathStyleAccess,
pathStyleAccessWarning = true,
accessStyle = if (pathStyleAccess) PathAccessStyle else VirtualHostAccessStyle,
endpointUrl,
listBucketApiVersion,
forwardProxy = None,
Expand All @@ -498,8 +545,7 @@ object S3Settings {
bufferType,
credentialsProvider,
s3RegionProvider,
pathStyleAccess = false,
pathStyleAccessWarning = true,
accessStyle = VirtualHostAccessStyle,
endpointUrl = None,
listBucketApiVersion,
forwardProxy = None,
Expand Down
Loading

0 comments on commit 639acb6

Please sign in to comment.