@DanielDeogun @DanielSawano DevDays 2017, Vilnius Secure by Design Daniel & Daniel @DanielDeogun @DanielSawano DevDays, Vilnius 2017
A presentation at DevDays Vilnius 2017 in May 2017 in Vilnius, Lithuania by Daniel Sawano
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Secure by Design Daniel & Daniel @DanielDeogun @DanielSawano DevDays, Vilnius 2017
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Daniel Deogun Daniel Sawano About us…
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Secure by Design Secure by Design is a new approach to software security that lets you create secure software while still focusing on business features.
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Secure by Design “Any activity involving active decision making
should be considered part of the software design process and can thus be referred to as design .”
@DanielDeogun @DanielSawano DevDays 2017, Vilnius What we’ll cover today… • Domain Primitives • Entity Snapshots • Dealing with Legacy Code • Security in your Pipelines Design Patterns Security & tests
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Domain Primitives A value object so precise in its definition that it, by its mere existence, manifests its validity is called a domain primitive .
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Domain Primitives • A Domain Primitive is very strict in its definition
• If it’s not valid then it cannot exist • Defined in the current domain
• It’s preciseness brings robustness in your code
• It’s immutable so it will always be valid
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Domain Primitives import static org.apache.commons.lang3.Validate.inclusiveBetween; import static org.apache.commons.lang3.Validate.notNull; public final class Quantity { private final int value; public Quantity(final int value) { inclusiveBetween(1, 200, value); this.value = value;
} public int value() { return value;
} public Quantity add(final Quantity addend) { notNull(addend); return new Quantity(value + addend.value); } // ... } Quantity is not just an int! • Enforces invariants at creation • Provides domain operations to • Encapsulate domain behavior
@DanielDeogun @DanielSawano DevDays 2017, Vilnius CIA • Confidentiality
• Integrity
• Availability
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Domain Primitives import static org.apache.commons.lang3.Validate.inclusiveBetween; import static org.apache.commons.lang3.Validate.notNull; public final class Quantity { private final int value; public Quantity(final int value) { inclusiveBetween(1, 200, value); this.value = value;
} public int value() { return value;
} public Quantity add(final Quantity addend) { notNull(addend); return new Quantity(value + addend.value); } // ... } Quantity is not just an int! • Enforces invariants at creation • Provides domain operations to • Encapsulate domain behavior
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Domain Primitives External context Your context Email Email Defined by RFC Defined by you
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Domain Primitives Use Domain Primitives as: • the smallest building block in your domain model • to build your Domain Primitive Library • to harden your code and your APIs
@DanielDeogun @DanielSawano DevDays 2017, Vilnius What we’ll cover today… ✓ Domain Primitives • Entity Snapshots • Dealing with Legacy Code • Security in your Pipelines Design Patterns Security & tests
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Entities • An entity has an identity that doesn’t change over time • The values/data belonging to an entity can change over time • Typically modeled as mutable objects
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Classic Entity public final class Order { private final OrderId id; private final List<OrderItem> orderItems = new ArrayList<>(); public Order(final OrderId id) { this.id = notNull(id); } public void addItem(final OrderItem item) { notNull(item); orderItems.add(item); }
// ... }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Perils of mutable state • Mutability is a source of security issues • Consistency in the presence of contention is hard • Contention can reduce availability
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Entity Snapshots Entity Snapshots are: • Securing mutable state by making it immutable • An immutable representation of a mutable entity • Solves many of the security problems with regular entities
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Entity Snapshots public final class Order { private final OrderId id; private final List<OrderItem> orderItems; public Order(final OrderId id, final List<OrderItem> orderItems) { noNullElements(orderItems); notNull(id); this.id = id; this.orderItems = unmodifiableList(new ArrayList<>(orderItems)); } public List<OrderItem> orderItems() { return orderItems; } // ... }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Entity Snapshots public final class WritableOrder { private final OrderId id; private final OrderRepository repository; public WritableOrder(final OrderId id, final OrderRepository repository) { this.id = notNull(id); this.repository = notNull(repository); } public void addOrderItem(final OrderItem orderItem) { notNull(orderItem); isOkToAdd(orderItem); repository.addItemToOrder(id, orderItem); } private void isOkToAdd(final OrderItem orderItem) { // domain validation logic to ensure it's ok to add order } }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius What we’ll cover today… ✓ Domain Primitives ✓ Entity Snapshots • Dealing with Legacy Code • Security in your Pipelines Design Patterns Security & tests
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Dealing with Legacy Code 3 good design patterns [6] Declutter Entities Harden your APIs Draw the Line [7] [8]
@DanielDeogun @DanielSawano DevDays 2017, Vilnius “Draw the Line” [6] • We need to identify the semantic boundary of a context • Add a layer that internally translates data to a domain primitive and the back again
data -> domain primitive -> data • This way, we have created a validation boundary that protects the inside from bad input • But, if rejecting data is to harsh, consider logging it for insight
@DanielDeogun @DanielSawano DevDays 2017, Vilnius “Harden the API” • Create a library of domain primitives • Express your APIs with your domain primitives • Never accept generic input if you have specific requirements Generic Specific [7] void buyBook(ISBN, Quantity) void buyBook(String, int)
@DanielDeogun @DanielSawano DevDays 2017, Vilnius “Decluttering Entities” [8] import static org.apache.commons.lang3.Validate.notNull; import static org.apache.commons.lang3.Validate.isTrue; public class Order { private final List<Object> items; private boolean paid; public void addItem(String isbn, int qty) { if (this.paid == false) { notNull(isbn); isTrue(isbn.length() == 10); isTrue(isbn.matches("[0-9X]*")); isTrue(isbn.matches(“[0-9]{9}[0-9X]”)); if (inventory.avaliableBooks(isbn, qty)) {
Book book = bookcatalogue.findBy(isbn); items.add(new OrderLine(book, qty)); } } }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius “Decluttering Entities” [8] import static org.apache.commons.lang3.Validate.notNull; import static org.apache.commons.lang3.Validate.isTrue; public class Order { private final List<Object> items; private boolean paid; public void addItem(final ISBN isbn, final Quantity quantity) { notNull(isbn); notNull(quantity); isTrue(notPaid());
if (inventory.avaliableBooks(isbn, quantity)) {
Book book = bookcatalogue.findBy(isbn); items.add(new OrderLine(book, quantity)); } }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius What we’ll cover today… ✓ Domain Primitives ✓ Entity Snapshots ✓ Dealing with Legacy Code • Security in your Pipelines Design Patterns Security & tests
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Security in your Pipelines [10] [12]
@DanielDeogun @DanielSawano DevDays 2017, Vilnius The Hospital Case [9]
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Email Domain Primitive External context Your context Email Email Defined by RFC Defined by you
@DanielDeogun @DanielSawano DevDays 2017, Vilnius The Domain Rules • The format of an email address must be local-part@domain
• The local part cannot be longer than 64 characters
• The domain must be hospital.com
• Subdomains are not accepted • The minimum length of an email address is 15 characters
• The maximum length of an email address is 77 characters
• The local part may only contain alphabetic characters (a-z), digits (0-9), and one period • The local part may not start or end by a period [5]
@DanielDeogun @DanielSawano DevDays 2017, Vilnius
Testing
Normal
Behavior
•
F
ocus on input that clearly meets the domain rules
class EmailAddressTest {
@TestFactory
Stream<DynamicTest> should_be_a_valid_address() {
return Stream.of(
"jane@hospital.com",
"jane01@hospital.com",
"jane.doe@hospital.com")
.map(input -> dynamicTest("Accepted: " + input,
() -> new EmailAddress(input)));
}
}
@DanielDeogun @DanielSawano DevDays 2017, Vilnius 1 st version of EmailAddress public final class EmailAddress { public final String value; public EmailAddress(final String value) {
matchesPattern
(value.toLowerCase(),
"^[a-z0-9]+\.?[a-z0-9]+@\bhospital.com$");
this.value = value.toLowerCase();
}
...
}
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Testing Boundary B ehavior
@TestFactory
Stream<DynamicTest> should_be_accepted() {
return Stream.of(
"aa@hospital.com",
repeat("X", 64) + "@hospital.com")
.map(input -> dynamicTest("Accepted: " + input,
() -> new EmailAddress(input)));
}
Email
Rejection • Reject an address that's 14 characters long • Reject an address with a local part that's 65 characters long • Reject an address with a local part containing an invalid character • Reject an address with multiple '@' symbols • Reject an address with a domain other than hospital.com
• Reject an address with a subdomain • Reject an address with a local part that starts with a period • Reject an address with a local part that ends with a period • Reject an address with sequential periods in the local part
Rejection
@TestFactory
Stream<DynamicTest> should_be_rejected() {
return Stream.of(
"a@hospital.com",
repeat("X", 65) + "@hospital.com",
"address_with_invalid_char_in_local_part@hospital.com",
"jane@doe@hospital.com",
"jane.doe@hospital.lt
",
"jane.doe@hospital.io",
"jane.doe@hospital.gov",
"jane.doe@example.com",
"jane.doe@cardio
.hospital.com",
".jane.doe@hospital.com",
"jane.doe.@hospital.com",
"jane..doe@hospital.com")
.map(input -> dynamicTest("Rejected: " + input,
assertInvalidEmail(input)));
}
@DanielDeogun @DanielSawano DevDays 2017, Vilnius 2 nd version of EmailAddress public final class EmailAddress { public final String value; public EmailAddress(final String value) {
matchesPattern (value.toLowerCase(), "^ (?=[a-z0-9.@]{15,77}$) [a-z0-9]+\.?[a-z0-9]+@\bhospital.com$"); this.value = value.toLowerCase(); } ... }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Testing with Invalid Input
• Any input that doesn't satisfy the domain rules is considered invalid
•
For some reason, null
, empty strings, or
"strange" characters tend to
result in
unexpected behavior
Email
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Testing with Invalid Input
@TestFactory
Stream<DynamicTest> should_reject_invalid_input() {
return Stream.of(
null,
"null",
"nil",
"0",
"",
" ",
" ",
"
",
"john.doe
@hospital.com",
" @hospital.com",
"%20@hospital.com",
"john.d%20e@hospital.com",
"john.doe.jane@hospital.com",
"--",
"e x a m p l e @ hospital . c o m",
"=0@$*^%;<!->.:\()&#"")
.map(input -> dynamicTest("Rejected: " + input,
assertInvalidEmail(input))); }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius 3 rd version of EmailAddress public final class EmailAddress { public final String value; public EmailAddress(final String value) {
notNull (value, "Input cannot be null");
matchesPattern (value.toLowerCase(), "^(?=[a-z0-9.@]{15,77}$)[a-z0-9]+\.?[a-z0-9]+@\bhospital.com$"); this.value = value.toLowerCase(); } ... }
@DanielDeogun @DanielSawano DevDays 2017, Vilnius
Testing with
Extreme
Input
•
Testing the extreme is all about identifying weaknesses in the
design that makes the application break or behave
strangely
when handling extreme values.
@TestFactory
Stream<DynamicTest> should_reject_extreme_input() {
return Stream.<Supplier<String>>of(
() -> repeat("x
", 10000),
() -> repeat("x
", 100000),
() -> repeat("x
", 1000000),
() -> repeat("x
", 10000000),
() -> repeat("x
", 20000000),
() -> repeat("x
", 40000000))
.map(input -> dynamicTest("Rejecting extreme input",
assertInvalidEmail(input.get())));
}
@DanielDeogun @DanielSawano DevDays 2017, Vilnius
Security weaknesses caused by
inefficient backtracking
v.s
"^[a-z0-9]+\.?[a-z0-9]+@\bhospital.com$"
"^
(?=[a-z0-9.@]{15,77}$)
[a-z0-9]+\.?[a-z0-9]+@\bhospital.com$"
@DanielDeogun @DanielSawano DevDays 2017, Vilnius What we have covered… ✓ Domain Primitives ✓ Entity Snapshots ✓ Dealing with Legacy Code ✓ Security in your Pipelines Design Patterns Security & tests
@DanielDeogun @DanielSawano DevDays 2017, Vilnius URL http s://manning.com Discount code:
ctwdevdays Part 1: Introduction
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Q&A [2]
@DanielDeogun @DanielSawano DevDays 2017, Vilnius Thank you! @DanielSawano @DanielDeogun
@DanielDeogun @DanielSawano DevDays 2017, Vilnius References [1] Book cover, Secure by Design, Manning Publication [2] Question mark, https://flic.kr/p/9ksxQa by Damián Navas under license https://creativecommons.org/licenses/by-nc-nd/2.0/
[3] DDos Attack, Secure by Design, Manning Publication
[4] Uber vs Ola,
https://www.bloomberg.com/news/articles/2016-03-23/uber-sues-ola-claiming-fake-bookings-as-india-fight-escalates
[5] Lyft vs Uber, http://time.com/3102548/lyft-uber-cancelling-rides/
[6] Boundary, https://flic.kr/p/nEZKMd by Graeme Fowler under license https://creativecommons.org/licenses/by/2.0/
[7] 3d key, https://flic.kr/p/e9qfrf by Yoel Ben-Avraham under license https://creativecommons.org/licenses/by-nd/2.0/
[8] Building blocks, https://flic.kr/p/agPw7C by Tiffany Terry under license https://creativecommons.org/licenses/by/2.0/
[9] Doctors Stock Photo, https://flic.kr/p/HNJUzV , by Sergio Santos under license https://creativecommons.org/licenses/by/2.0/
[10] Anonymous, https://flic.kr/p/a2eniw, by Lio Leiser under license https://creativecommons.org/licenses/by-nd/2.0/
[11] CIA, https://goo.gl/images/DRzRcp
[12] Bilfinger - Natural gas pipeline USA, https://flic.kr/p/nrFHFz by Bilfinger SE under license https://creativecommons.org/licenses/by-nd/2.0/