📄 Page
1
M A N N I N G Dan Bergh Johnsson Daniel Deogun Daniel Sawano Foreword by Daniel Terhorst-North
📄 Page
2
MANN I NG Shelter ISland Secure by Design DAN BERGH JOHNSSON DANIEL DEOGUN DANIEL SAWANO Foreword by Daniel Terhorst-North
📄 Page
3
For online information and ordering of this and other Manning books, please visit www.manning.com. The publisher offers discounts on this book when ordered in quantity. For more information, please contact Special Sales Department Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 Email: orders@manning.com ©2019 by Manning Publications Co. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps. Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid- free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine. ∞ Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 ISBN 9781617294358 Printed in the United States of America Development editor: Jennifer Stout Technical development editor: Luis Atencio Review editor: Aleks Dragosavljević Production editor: David Novak Copy editor: Frances Buran Proofreader: Carl Quesnel Technical proofreader: John Guthrie Typesetter: Happenstance Type-O-Rama Cover designer: Marija Tudor
📄 Page
4
To our families —Dan Bergh Johnsson, Daniel Deogun, and Daniel Sawano
📄 Page
5
(This page has no text content)
📄 Page
6
v brief contents Part 1 Introduction. .............................................................. 1 1 ■ Why design matters for security 3 2 ■ Intermission: The anti-Hamlet 31 Part 2 Fundamentals .............................................................47 3 ■ Core concepts of Domain-Driven Design 49 4 ■ Code constructs promoting security 87 5 ■ Domain primitives 113 6 ■ Ensuring integrity of state 137 7 ■ Reducing complexity of state 164 8 ■ Leveraging your delivery pipeline for security 189 9 ■ Handling failures securely 223 10 ■ Benefits of cloud thinking 251 11 ■ Intermission: An insurance policy for free 278 Part 3 Applying the fundamentals ....................................293 12 ■ Guidance in legacy code 295 13 ■ Guidance on microservices 322 14 ■ A final word: Don’t forget about security! 343
📄 Page
7
(This page has no text content)
📄 Page
8
vii contents foreword xv preface xix acknowledgments xxi about this book xxiii about the authors xxvii about the cover illustration xxix Part 1 Introduction .................................................1 1 Why design matters for security 3 1.1 Security is a concern, not a feature 5 The robbery of Öst-Götha Bank, 1854 5 ■ Security features and concerns 6 ■ Categorizing security concerns: CIA-T 8 1.2 Defining design 9 1.3 The traditional approach to software security and its shortcomings 11 Explicitly thinking about security 14 ■ Everyone is a security expert 14 ■ Knowing all and the unknowable 14 1.4 Driving security through design 14 Making the user secure by design 15 ■ The advantages of the design approach 18 ■ Staying eclectic 21
📄 Page
9
viii contents 1.5 Dealing with strings, XML, and a billion laughs 21 Extensible Markup Language (XML) 22 ■ Internal XML entities in a nutshell 22 ■ The Billion Laughs attack 23 ■ Configuring the XML parser 23 ■ Applying a design mindset 25 ■ Applying operational constraints 28 ■ Achieving security in depth 28 2 Intermission: The anti-Hamlet 31 2.1 An online bookstore with business integrity issues 33 The inner workings of the accounts receivable ledger 35 ■ How the inventory system tracks books in the store 36 ■ Shipping anti- books 37 ■ Systems living the same lie 38 ■ A do-it-yourself discount voucher 38 2.2 Shallow modeling 39 How shallow models emerge 41 ■ The dangers of implicit concepts 42 2.3 Deep modeling 43 How deep models emerge 43 ■ Make the implicit explicit 45 Part 2 Fundamentals..............................................47 3 Core concepts of Domain-Driven Design 49 3.1 Models as tools for deeper insight 51 Models are simplifications 53 ■ Models are strict 56 ■ Models capture deep understanding 59 ■ Making a model means choosing one 61 ■ The model forms the ubiquitous language 63 3.2 Building blocks for your model 65 Entities 66 ■ Value objects 70 ■ Aggregates 73 3.3 Bounded contexts 77 Semantics of the ubiquitous language 77 ■ The relationship between language, model, and bounded context 78 ■ Identifying the bounded context 78 3.4 Interactions between contexts 81 Sharing a model in two contexts 82 ■ Drawing a context map 83 4 Code constructs promoting security 87 4.1 Immutability 88 An ordinary webshop 88 ■ Failing fast using contracts 95 Checking preconditions for method arguments 97 ■ Upholding invariants in constructors 99 ■ Failing for bad state 101
📄 Page
10
ixcontents 4.3 Validation 102 Checking the origin of data 103 ■ Checking the size of data 105 Checking lexical content of data 107 ■ Checking the data syntax 109 ■ Checking the data semantics 110 5 Domain primitives 113 5.1 Domain primitives and invariants 114 Domain primitives as the smallest building blocks 114 ■ Context boundaries define meaning 116 ■ Building your domain primitive library 118 ■ Hardening APIs with your domain primitive library 119 ■ Avoid exposing your domain publicly 120 5.2 Read-once objects 121 Detecting unintentional use 123 ■ Avoiding leaks caused by evolving code 125 5.3 Standing on the shoulders of domain primitives 127 The risk with overcluttered entity methods 127 ■ Decluttering entities 129 ■ When to use domain primitives in entities 132 5.4 Taint analysis 133 6 Ensuring integrity of state 137 6.1 Managing state using entities 138 6.2 Consistent on creation 140 The perils of no-arg constructors 140 ■ ORM frameworks and no-arg constructors 142 ■ All mandatory fields as constructor arguments 143 ■ Construction with a fluent interface 147 Catching advanced constraints in code 149 ■ The builder pattern for upholding advanced constraints 151 ■ ORM frameworks and advanced constraints 155 ■ Which construction to use when 155 6.3 Integrity of entities 156 Getter and setter methods 156 ■ Avoid sharing mutable objects 158 ■ Securing the integrity of collections 160 7 Reducing complexity of state 164 7.1 Partially immutable entities 166 7.2 Entity state objects 168 Upholding entity state rules 168 ■ Implementing entity state as a separate object 172
📄 Page
11
x contents 7.3 Entity snapshots 174 Entities represented with immutable objects 175 ■ Changing the state of the underlying entity 177 ■ When to use snapshots 180 7.4 Entity relay 181 Splitting the state graph into phases 183 ■ When to form an entity relay 186 8 Leveraging your delivery pipeline for security 189 8.1 Using a delivery pipeline 190 8.2 Securing your design using unit tests 191 Understanding the domain rules 192 ■ Testing normal behavior 193 ■ Testing boundary behavior 194 ■ Testing with invalid input 197 ■ Testing the extreme 200 8.3 Verifying feature toggles 201 The perils of slippery toggles 201 ■ Feature toggling as a development tool 202 ■ Taming the toggles 205 ■ Dealing with combinatory complexity 209 ■ Toggles are subject to auditing 209 8.4 Automated security tests 210 Security tests are only tests 210 ■ Working with security tests 211 ■ Leveraging infrastructure as code 212 ■ Putting it into practice 212 8.5 Testing for availability 213 Estimating the headroom 213 ■ Exploiting domain rules 215 8.6 Validating configuration 216 Causes for configuration-related security flaws 216 ■ Automated tests as your safety net 218 ■ Knowing your defaults and verifying them 219 9 Handling failures securely 223 9.1 Using exceptions to deal with failure 224 Throwing exceptions 225 ■ Handling exceptions 227 ■ Dealing with exception payload 231 9.2 Handling failures without exceptions 232 Failures aren’t exceptional 232 ■ Designing for failures 234 9.3 Designing for availability 237 Resilience 237 ■ Responsiveness 238 ■ Circuit breakers and timeouts 238 ■ Bulkheads 241
📄 Page
12
xicontents 9.4 Handling bad data 244 Don’t repair data before validation 245 ■ Never echo input verbatim 247 10 Benefits of cloud thinking 251 10.1 The twelve-factor app and cloud-native concepts 252 10.2 Storing configuration in the environment 253 Don’t put environment configuration in code 254 ■ Never store secrets in resource files 255 ■ Placing configuration in the environment 256 10.3 Separate processes 258 Deploying and running are separate things 258 ■ Processing instances don’t hold state 259 ■ Security benefits 261 10.4 Avoid logging to file 261 Confidentiality 262 ■ Integrity 262 ■ Availability 263 Logging as a service 263 10.5 Admin processes 266 The security risk of overlooked admin tasks 267 ■ Admin tasks as first-class citizens 267 10.6 Service discovery and load balancing 269 Centralized load balancing 269 ■ Client-side load balancing 270 ■ Embracing change 271 10.7 The three R’s of enterprise security 271 Increase change to reduce risk 272 ■ Rotate 273 ■ Repave 274 Repair 276 11 Intermission: An insurance policy for free 278 11.1 Over-the-counter insurance policies 279 11.2 Separating services 280 11.3 A new payment type 281 11.4 A crashed car, a late payment, and a court case 285 11.5 Understanding what went wrong 287 11.6 Seeing the entire picture 287 11.7 A note on microservices architecture 292
📄 Page
13
xii contents Part 3 Applying the fundamentals .................... 293 12 Guidance in legacy code 295 12.1 Determining where to apply domain primitives in legacy code 296 12.2 Ambiguous parameter lists 297 The direct approach 299 ■ The discovery approach 300 The new API approach 302 12.3 Logging unchecked strings 303 Identifying logging of unchecked strings 303 ■ Identifying implicit data leakage 304 12.4 Defensive code constructs 305 Code that doesn’t trust itself 306 ■ Contracts and domain primitives to the rescue 308 ■ Overlenient use of Optional 309 12.5 DRY misapplied—not focusing on ideas, but on text 310 A false positive that shouldn’t be DRY’d away 311 ■ The problem of collecting repeated pieces of code 311 ■ The good DRY 312 ■ A false negative 312 12.6 Insufficient validation in domain types 313 12.7 Only testing the good enough 315 12.8 Partial domain primitives 316 Implicit, contextual currency 317 ■ A U.S. dollar is not a Slovenian tolar 318 ■ Encompassing a conceptual whole 319 13 Guidance on microservices 322 13.1 What’s a microservice? 323 Independent runtimes 324 ■ Independent updates 324 ■ Designed for down 324 13.2 Each service is a bounded context 325 The importance of designing your API 326 ■ Splitting monoliths 328 ■ Semantics and evolving services 329 13.3 Sensitive data across services 329 CIA-T in a microservice architecture 330 ■ Thinking “sensitive” 331 13.4 Logging in microservices 332 Integrity of aggregated log data 333 ■ Traceability in log data 334 Confidentiality through a domain-oriented logger API 337
📄 Page
14
xiiicontents 14 A final word: Don’t forget about security! 343 14.1 Conduct code security reviews 344 What to include in a code security review 345 ■ Whom to include in a code security review 346 14.2 Keep track of your stack 346 Aggregating information 346 ■ Prioritizing work 347 14.3 Run security penetration tests 347 Challenging your design 348 ■ Learning from your mistakes 349 ■ How often should you run a pen test? 349 Using bug bounty programs as continuous pen testing 350 14.4 Study the field of security 351 Everyone needs a basic understanding about security 351 Making security a source of inspiration 352 14.5 Develop a security incident mechanism 353 Incident handling 354 ■ Problem resolution 354 ■ Resilience, Wolff’s law, and antifragility 356 index 361
📄 Page
15
(This page has no text content)
📄 Page
16
xv foreword In the early 1990s I was in my first graduate job in the middle of a recession, and they were having a tough round of layoffs. Someone noticed that each victim’s UNIX account was being locked out just before the friendly HR person came to tap them on the shoulder and escort them from the building. They wrote a small script to monitor differences in the user password file and display the names of users whose accounts were being locked. We suddenly had a magic tool that would identify the next target just before the hatchet fell...and an enormous security and privacy breach. In my second job, as a programmer at a marketing firm, there were lots of pass- word-protected Microsoft Word documents flying around, often with sensitive commer- cial information in them. I pointed out how weak the encryption was on these files, and how easy it was to read them using a freely available tool that was making the rounds on Usenet (your grandparents’ Google Groups). No one listened until I started emailing the files back to the senders with the encryption removed. Then I figured most people’s login passwords were probably too weak as well. I got the same lack of response until I wrote a script that ran a simple password-cracking tool on a regular basis, and emailed people their login passwords. There was a pretty high hit rate. At that stage I didn’t know anything about information theory, Shannon entropy, attack surface areas, asymmetric cryptography—I was just a kid with a password-crack- ing tool. But I became the company’s de facto InfoSec Officer. Those were simpler times! Over a decade later, as a developer at ThoughtWorks building a large-scale energy trading platform, I received what is still my favorite ever bug report. One of our testers noticed that a password field didn’t have a check for password length, which should have been 30 characters. However, she didn’t log the bug as “30 character password limit isn’t being checked.” Instead, she thought “I wonder how much text I could shove
📄 Page
17
xvi foreword into that password field?” By a process of trial and error, the final bug report was “If you enter more than 32,000 characters in the password field, then the application crashes.” She had turned a simple validation error into a denial-of-service security exploit, crashing the entire application server just by entering a suitably crafted password. (Some years later I was at a software testing conference where they decided to use iPads for confer- ence registration, using an app they had written themselves. I learned you should never do this with software testers, when a tester friend tried registering as “Julie undefined” and brought the whole system to its knees. Testers are evil.) Fast-forward another decade or so to the present day, and I watch in dismay as nearly every week yet another data security breach of a high-profile company appears in the news. I could cite some recent ones, but they will be ancient history by the time you read this, and newer, bigger, more worrying data hauls of passwords, phone numbers, credit card details, social security numbers, and other sensitive personal and financial data will have appeared on the dark web, only to be discovered and reported months or years later to an increasingly desensitized and vulnerable public. Why is this picture so bleak? In a world of free multifactor authentication, biomet- ric security, physical tokens, password suites like 1Password (https://1password.com/) and LastPass (https://www.lastpass.com/), and notification services like Have I Been Pwned (https://haveibeenpwned.com), you could be forgiven for thinking we’ve got security covered. But as Dan, Daniel, and Daniel point out in the introduction (I felt obliged to write this foreword on the basis there weren’t enough people called Daniel involved), there is no point having strong locks and heavy doors if a malicious actor can just lift the doors off their metaphorical hinges and walk off with the prize. There is no such thing as a secure system, at least not in absolute terms. All security is relative to a perceived threat model, and all systems are more or less secure with respect to that model. The goal of this book, and the reason its content has never been more urgent or relevant, is to demonstrate that security is first and foremost a design consideration. It isn’t something you can graft on at the end, however well-intentioned you are. Security is in the data types you choose, and how you represent them in code. Secu- rity is in the domain terms you use, and how faithfully you model domain concepts and business rules. Security is in reducing the cognitive distance between the business domain and the tools you build to address customer needs in that domain. As the authors demonstrate again and again throughout this book, reducing this cognitive distance eliminates entire classes of security risk. The easier we can make it for domain experts to recognize concepts and processes in the way we model a solu- tion, and in the corresponding code, tests, and other technical artifacts, the more likely they are to spot problems. They can call out the discrepancies, inconsistencies, assump- tions, and all the other myriad ways we build systems that don’t reflect the real world: online bookstores where you can buy a negative number of books, password fields that allow you to submit a decent-sized sonnet, and sensitive account information that can be viewed by casual snoopers. Secure by Design is my favorite kind of book for two reasons. First, it weaves together two of my favorite fields: Application and Information Security—in which I am an
📄 Page
18
xviiforeword enthusiastic amateur—and Domain-Driven Design—in which I hope I can claim some kind of proficiency. Second, it is a practical, actionable handbook. It isn’t just a call to arms about treating security seriously as a design activity, which would be a worthy goal in its own right, it also provides a raft of real examples, worked through from design considerations to actual code listings, that put meat on the bones of security by design. I want to note a couple of standout examples, though there are many. One is the treatment of “shallow design,” exemplified by using primitive types like integers and strings to represent rich business concepts. This exposes you to risks like the password exploit (a Password type would be self-validating for length, say, in a way a string isn’t), or the negative books (a BookCount type wouldn’t allow negative values like an integer does). Reading this section, as someone who has been writing software professionally for over 30 years, I wanted to reach back through time and hit my younger program- ming self on the head with this book, or at least leave it mysteriously on his desk with an Alice in Wonderland-style Read Me label on it. Another exemplar is the topic of poor error handling, which is a huge source of potential security violations. Most modern programming languages have two types of code paths: the ones where things go OK, and the ones where bad things happen. The latter mostly live in a twilight zone of catch-blocks and exception handlers, or half- hearted guard clauses. As programmers, our cognitive biases conspire to convince us we have covered all the cases. We even have the hubris to write comments like // this can’t happen. We are wrong again and again. The late Joe Armstrong, an amazing systems engineer and inventor of the Erlang language, used to say that the only reliable way to handle an error is to “Let it crash!” The contortions we go through to avoid “letting it crash” range from the “billion-dollar mistake” of null pointers and their tricksy exceptions, through nested if-else stacks and the will-they-won’t-they fall-through logic of switch blocks, to leaning on our IDEs to generate the arcane boilerplate code for interpolating strings or evaluating equality. We know smaller components are easier to test than larger ones. They have exponen- tially fewer places for bugs to hide, and it is therefore easier to reason about their secu- rity. However, we are only beginning to understand the security implications of running a system of hundreds or thousands of small components—microservices or serverless architectures—and the fledgling domains of Observability and Chaos Engineering are starting to gain mindshare in a way DevOps and Continuous Delivery did before them. I see Secure by Design as an important contribution to this trajectory, but focusing on the very heart of the development cycle, in the domain-modeling activities that DDD folks refer to as knowledge crunching, and leveraging the ideas of ubiquitous language and bounded contexts to bring security to the fore in programming, testing, deployment, and runtime. Shallow modeling and post hoc security audits don’t cut it anymore. We can’t all be security experts. We can all be mindful of good Domain-Driven Design and its consequent impact on security. Daniel Terhorst-North, Security Amateur, London, July 2019
📄 Page
19
(This page has no text content)
📄 Page
20
xix preface As developers, good design feels natural to us. Even before we met, all three of us enjoyed good code: code that speaks its intention, that captures the ideas of its creators in ways that are easy to understand, and that’s intuitive to work with. We assume you also like good code. We also share a common interest in security, realizing both how important and how hard that work is. The digitization of our world is a marvelous thing, but bad security is one of the things that can undermine it. Over the years, we’ve met and worked with lots of people. We’ve discussed code and design in general, and security in particular. The idea that high-quality programming practices can reduce the number of security-related mistakes gradually took hold and grew. If programmers could have that kind of support at their fingertips, it could have a tremendous impact, making our world a little bit more stable. This is the idea that later became secure by design and this book. Independently, we’ve tried and tested that idea in various forms, most of which never got a name, and we’ve met and exchanged ideas with many people. Some of these exchanges have left a somewhat bigger imprint and deserve mentioning—at the risk of not mentioning other important exchanges. Some important influences came from Eric Evans. His ideas about Domain-Driven Design (DDD) provided a terminology to talk about how code should capture meaning. In 2008, security researcher John Wilander and DDD enthusiast Dan Bergh Johnsson began to work together, and security entered the mix. The ideas from DDD came to form the platform for their discussions about security and code. Together, they coined the phrase Domain-Driven Security in 2009, which was one of the first-named front-runners to secure by design. Upon presenting at the OWASP European conference in 2010, they realized that Erlend Oftedal in Oslo had been playing with similar ideas, and the discus- sion broadened. These discussions led to a deeper understanding of how to mitigate risks