Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object...

Post on 02-Jun-2020

2 views 0 download

Transcript of Security In Scala - Lightbend · 2018-06-28 · Security In Scala Rened Types and Object...

Security In ScalaRe�ned Types and Object Capabilities

Will Sargent • @will_sargent

https://tersesystems.com/

1 / 50

Many New Things!

2 / 50

What is Security in Scala?

3 / 50

Validate untrusted input.

Hide internal state and behavior.

Deny functionality to attackers.

4 / 50

Security DecisionsValidation - deciding what is valid.

Abstraction - deciding what should be visible.

Encapsulation - deciding what should be hidden.

Access Control - deciding what is denied.

5 / 50

Security ToolsRefinement Types - validation.

Capabilities - abstraction & encapsulation & revocable access.

Dynamic Sealing - encapsulation & information hiding.

Membranes - operational isolation & access control.

6 / 50

What are Re�nement Types?

7 / 50

Refinement Types are raw types that obey predicates.

8 / 50

Re�nement TypesExamples of raw types: String, Boolean, Character, Int, Collection API.

Raw types are unconstrained, and could contain invalid or harmful input.

In particular, String can contain anything.

9 / 50

String Fail

10 / 50

Re�ned Libraryrefined: simple refinement types for Scala

libraryDependencies += "eu.timepit" %% "refined" % "0.9.0"

https://github.com/fthomas/refined

11 / 50

Beforeval requestParameter: String = "94111"

// XXX How do we know this is a zip code?val zipCode: String = requestParameter

12 / 50

Afterscala> import eu.timepit.refined._import eu.timepit.refined._

scala> import eu.timepit.refined.api.Refinedimport eu.timepit.refined.api.Refined

scala> import eu.timepit.refined.string._import eu.timepit.refined.string._

scala> import eu.timepit.refined.auto._import eu.timepit.refined.auto._

scala> type ZipCode = String Refined MatchesRegex[W.`"""\\d{5}"""`.T]defined type alias ZipCode

scala> val requestParameter: String = "94111"requestParameter: String = 94111

scala> val v: Either[String, ZipCode] = refineV(requestParameter)v: Either[String,ZipCode] = Right(94111)

13 / 50

Re�nement Types AdvantagesYou can't get a ZipCode without going through refineV.

You can pass a ZipCode around anywhere you would use a String.

Move from "Stringly Typed" code to "Strongly Typed" code!

14 / 50

What are Capabilities?

15 / 50

A capability is a security primitive that confers authority by reference.

Authority is sufficient justification to affect a resource.

16 / 50

De�ning Capabilities in ScalaIn Scala:

A resource is an object with some sensitive fields and/or methods.

A capability is a reference to an object that can affect the resource.

Distinct from OOP!

OOP is all about accessibility through abstraction.

"Here is a Cake with an eat method!"

Capabilities are all about inaccessability through encapsulation.

"If you don't have access to the Cake, you can't eat the cake."

17 / 50

Capability ExampleWe have a Document object, which has a name field anyone can change.

We want to expose the ability to change the name as a capability.

18 / 50

Document Beforefinal class Document(var name: String) { override def toString: String = s"Document($name)"}

val document = new Document("Steve")document.name = "Will"

19 / 50

Isolate Name as NameChangerfinal class Document(private var name: String) { private object capabilities { val nameChanger = new Document.NameChanger { override def changeName(newName: String): Unit = { name = newName } } } override def toString: String = s"Document($name)"}

20 / 50

De�ne NameChangerobject Document { trait NameChanger { def changeName(name: String): Unit }

class Access { def nameChanger(doc: Document): NameChanger = { doc.capabilities.nameChanger } }}

21 / 50

Usageval document = new Document("Steve")// document: CapExample.Document = Document(Steve)

val access = new Document.Access()// access: CapExample.Document.Access = CapExample$Document$Access@577d1ce5

val nameChanger = access.nameChanger(document)// nameChanger: CapExample.Document.NameChanger = CapExample$Document$capabilities$

nameChanger.changeName("Will")

println(s"result = $document")// result = Document(Will)

22 / 50

Isolation of ObjectsThe nameChanger capability can affect the resource (Document object) bychanging its name.

Access to the resource (Document object) does not mean you have acapability on that resource.

The only way to get access to a NameChanger instance is throughDocument.Access, which is usually sealed.

Note that nameChanger is a "root level" capability! For delegation, youwould use Revocable.

23 / 50

More Advanced ExampleWe've got a standard CRUD ItemRepository object as the resource.

By definition, everyone has access to create, read, update, and delete!

Principle of Least Authority (POLA) says "only give people the power theyneed"

If an operation has no need of delete functionality, then don't hand it out.

Also, let's make functional effects part of the API, because we are Try-hardprogammer.

24 / 50

ItemRepository Capabilitiesobject ItemRepository { trait Finder[F[_]] { def find(id: UUID): F[Option[Item]] }

trait Updater[F[_]] { def update(item: Item): F[UpdateResult] }

case class UpdateResult(message: String)

class Access { def finder(repo: ItemRepository): Finder[Id] = repo.capabilities.finder def updater(repo: ItemRepository): Updater[Id] = repo.capabilities.updater }}

25 / 50

ItemRepository Capabilitiesclass ItemRepository { import ItemRepository._

private def find(id: UUID): Option[Item] = items.find(_.id == id)

private def update(u: Item): UpdateResult = UpdateResult(s"item $u updated")

private object capabilities { val finder: Finder[Id] = new Finder[Id]() { override def find(id: UUID) = ItemRepository.this.find(id) }

val updater: Updater[Id] = new Updater[Id]() { override def update(item: Item) = ItemRepository.this.update(item) } }}

26 / 50

ItemRepository Usagescala> val repo: ItemRepository = new ItemRepository {}repo: RevokeExample.ItemRepository = $anon$1@1d3528e4

scala> val access = new ItemRepository.Access()access: RevokeExample.ItemRepository.Access = RevokeExample$ItemRepository$Access@

scala> val finder = access.finder(repo)finder: RevokeExample.ItemRepository.Finder[RevokeExample.Id] = RevokeExample$ItemR

scala> val maybeItem: Option[Item] = finder.find(ID)maybeItem: Option[RevokeExample.Item] = Some(Item(c31d34e2-5892-4a2d-9fd5-3ce2e0efe

27 / 50

ItemRepository Usage with E�ectsscala> import scala.util._import scala.util._

scala> class TryAccess(access: ItemRepository.Access) { | def finder(repo: ItemRepository): ItemRepository.Finder[Try] = (id: UUID) | Try(access.finder(repo).find(id)) | } | def updater(repo: ItemRepository): ItemRepository.Updater[Try] = (item: I | Try(access.updater(repo).update(item)) | } | }defined class TryAccess

scala> val tryAccess = new TryAccess(access)tryAccess: TryAccess = TryAccess@5373d34e

scala> // Create a finder which uses `Try` as an effect | val tryFinder = tryAccess.finder(repo)tryFinder: RevokeExample.ItemRepository.Finder[scala.util.Try] = TryAccess$$Lambda$

scala> // Now we know it won't throw exceptions :-) | val tryMaybeItem: Try[Option[Item]] = tryFinder.find(ID)tryMaybeItem: scala.util.Try[Option[RevokeExample.Item]] = Success(Some(Item(c31d34

28 / 50

RevocationRevocation takes back the ability to use a capability.

ocaps* implements macros and utility classes for revocation.

ocaps.Revocable - contains proxy that forwards to real capability andRevoker.

ocaps.Revoker - Signals to proxy that capability is revoked when revoke()is called.

* https://wsargent.github.io/ocaps/

29 / 50

Revocable Example

scala> // Call find as usual... | revocableFinder.find(ID) // throws RevokedException!ocaps.RevokedException: Capability revoked!

scala> import ocaps._import ocaps._

scala> import ocaps.macros._import ocaps.macros._

scala> // Create proxy finder that will forward "find" method to `finder` | // revocableFinder is also called a "caretaker" | val Revocable(revocableFinder, revoker) = revocable[ItemRepository.Finder[IdrevocableFinder: RevokeExample.ItemRepository.Finder[RevokeExample.Id] = $anon$1@3drevoker: ocaps.Revoker = ocaps.LatchRevoker@56b72d71

scala> // Call the revoker, which causes forwarding to stop... | revoker.revoke()

30 / 50

ocaps macros for working with capabilitiesDeals with the boilerplate of creating proxies with well-known semantics.

import ocaps.macros._

Composition: compose two capabilities together.

val fooWithBar: Foo with Bar = compose[Foo with Bar](foo, bar)

Attenuation: extract a capability from a composed one.

val foo: Foo = attenuate[Foo](fooWithBar)

Modulation: set up before/after hooks on capability.

val modulated: Foo = modulate[Foo](revocableDoer, beforeFunc,afterFunc)

Expiration: use modulation/revocation for time/use based capabilities.

31 / 50

Expiration Examapleval deadline = duration.fromNowval Revocable(revocableDoer, revoker) = revocable[Doer](doer)val before: String => Unit = { _ => if (deadline.isOverdue()) { revoker.revoke() }}val after: (String, Any) => Unit = { (_, _) =>() }val expiringDoer: Doer = modulate[Doer](revocableDoer, before, after)

32 / 50

What is Dynamic Sealing?

33 / 50

Wrapping an object in another class so that it cannot be seen or tamperedwith until it is unsealed.

34 / 50

Dynamic SealingDynamic sealing is a very old idea* that has a number of interesting uses.

The Morning Paper has a great writeup with a better explanation ofsealing than the paper itself.

Two functions: sealer and unsealer, which wrap and unwrap a Box.

Like default scope, but if you got to pick your siblings.

* Protection in programming languages, Morris Jr., CACM 1973

35 / 50

Dynamic Sealingscala> import ocaps._import ocaps._

scala> val (sealer, unsealer) = Brand.create[String]("string hint").tuplesealer: ocaps.Brand.Sealer[String] = ocaps.Brand$$Lambda$5237/981202539@6c7bd074unsealer: ocaps.Brand.Unsealer[String] = ocaps.Brand$$Lambda$5238/1024427149@836498

scala> val sealedBox = sealer("foo")sealedBox: ocaps.Brand.Box[String] = <sealed string hint box>

scala> val stringIsFoo = unsealer(sealedBox)stringIsFoo: Option[String] = Some(foo)

36 / 50

Dynamic Sealing UsesSeal credentials to avoid accidental leakage through logging.

Safely transport capabilities, Access, database connections in Akkamessages!

Signing / Trademarks - make the unsealer publicly available.

Encryption - make the sealer publicly available.

37 / 50

Dynamic Sealing Advanced Examaplescala> case class Food(name: String)defined class Food

scala> case class Can(food: Brand.Box[Food])defined class Can

scala> class CanOpener(unsealer: Brand.Unsealer[Food]) { | def open(can: Can): Food = unsealer(can.food).get | }defined class CanOpener

scala> val (sealer, unsealer) = Brand.create[Food]("canned food").tuplesealer: ocaps.Brand.Sealer[Food] = ocaps.Brand$$Lambda$5237/981202539@347bd6beunsealer: ocaps.Brand.Unsealer[Food] = ocaps.Brand$$Lambda$5238/1024427149@791e0ce

scala> val canOpener = new CanOpener(unsealer)canOpener: CanOpener = CanOpener@9a6ad8

scala> val canOfSpam: Can = Can(sealer(Food("spam")))canOfSpam: Can = Can(<sealed canned food box>)

scala> canOpener.open(canOfSpam)res10: Food = Food(spam)

38 / 50

What are Membranes?

39 / 50

An extension of a "forwarder" that transitively imposes a "policy" on allreferences exchanged via the membrane.

40 / 50

Wait, what?Okay, imagine you've got Future[A], and you want a Future[B].

flatMap, right?

A membrane provides Future and flatMap under the hood, so you thinkyou're working with A, but you're really working with Future[A].

Origins in E, Javascript, literally a footnote in PhD thesis.

41 / 50

Membrane BackgroundAll arguments and methods are "wrapped" with membrane and cannotescape.

Commonly used in sandboxes for "uncooperative revocation".

Tough to do directly in JVM, using ByteBuddy interceptors may bepossible.

Type safety doesn't mix well with message passing.

Probably possible to do using Akka actors, if you got rid of actor selection.

42 / 50

Permeable MembraneHowever. If you squint, a membrane wrapper is totally a dependentlytyped monad.

if you are willing to deal with the effect up front, assume "cooperativerevocation"...

...then PermeableMembrane is for you.

ocaps provides PermeableMembrane and RevokerMembrane out of the box.

43 / 50

Singleton Object De�nitionobject Location { trait LocaleReader[F[_]] { def locale: F[Locale] }

trait TimeZoneReader[F[_]] { def timeZone: F[TimeZone] }}

44 / 50

Class De�nitionclass Location(locale: Locale, timeZone: TimeZone) { import Location._ private object capabilities { val localeReader: LocaleReader[Id] = new LocaleReader[Id] { override val locale: Locale = Location.this.locale }

val timeZoneReader: TimeZoneReader[Id] = new TimeZoneReader[Id] { override val timeZone: TimeZone = Location.this.timeZone } }}

45 / 50

PermeableMembrane continuedclass MembraneAccess(access: Location.Access, val membrane: PermeableMembrane) { type Wrapper[+A] = membrane.Wrapper[A]

def localeReader(location: Location): LocaleReader[Wrapper] = { new LocaleReader[Wrapper] { override def locale: Wrapper[Locale] = { membrane.wrap(access.localeReader(location).locale) } } }

def timeZoneReader(location: Location): TimeZoneReader[Wrapper] = { new TimeZoneReader[Wrapper] { override def timeZone: Wrapper[TimeZone] = { membrane.wrap(access.timeZoneReader(location).timeZone) } } }}

46 / 50

PermeableMembrane Usageval m = RevokerMembrane()// m: ocaps.RevokerMembrane = ocaps.RevokerMembrane@16ff8bac

val location = new Location(Locale.US, TimeZone.getTimeZone("PST"))// location: MembraneExample.Location = MembraneExample$Location@53c05739

val access = new MembraneAccess(new Location.Access(), m)// access: MembraneExample.MembraneAccess = MembraneExample$MembraneAccess@a893610

val format = for { timeZone <- access.timeZoneReader(location).timeZone locale <- access.localeReader(location).locale} yield { ZonedDateTime.now(timeZone.toZoneId) .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL) .withLocale(locale))}// format: access.membrane.Wrapper[String] = ocaps.PermeableMembrane$Wrapper$$anon$

format.get// res11: String = Wednesday, June 20, 2018 9:21:39 AM PDT

47 / 50

Failure is always an option

format2.get// ocaps.RevokedException: Capability revoked!

val m2 = RevokerMembrane()// m2: ocaps.RevokerMembrane = ocaps.RevokerMembrane@5d464093

val access2 = new MembraneAccess(new Location.Access(), m2)// access2: MembraneExample.MembraneAccess = MembraneExample$MembraneAccess@2d18989

val format2 = for { timeZone <- access2.timeZoneReader(location).timeZone locale <- access2.localeReader(location).locale} yield { ZonedDateTime.now(timeZone.toZoneId) .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL) .withLocale(locale))}// format2: access2.membrane.Wrapper[String] = ocaps.PermeableMembrane$Wrapper$$ano

m2.revoke()

48 / 50

Questions!

49 / 50

Thanks!Documentation at https://wsargent.github.io/ocaps/

Guide at https://wsargent.github.io/ocaps/guide/index.html

50 / 50