Components
What is a component? There is not a straight-forward answer to this unfortunately. This is good in the sense it gives freedom to define this in a way that makes sense to your business, but bad in the sense it is not prescriptive and so may be challenging for people to understand what to capture.
It may help to understand how components are used:
First and foremost they are a list of all the systems that make up the system being threat modelled, and the systems connected to the system being threat modelled.
They allow scope to be defined. The choice of Components should allow it to be categorised as either in-scope or out-of-scope.
They allow authentication and authorization to be defined. A system that implements authentication and/or authorization is most likely a component.
If the system itself has an identity given to it e.g. service account, OAuth2 Client ID, then it is likely a component.
For the Team that owns the threat model:
any system they created should be considered a Component
any system or service they got provisioned or on-boarded (e.g. a cloud database, SaaS service) should be considered a component
any system they own or operate (e.g. Kubernetes cluster) should be considered a component
Components often correspond to deployable units’ of artifacts. The level of granularity of a sub-system that constitutes a Component should lean towards a single deployable thing e.g. a Kubernetes Pod and not the individual containers, a container hosted in ECS, a combined web and application server (if co-hosted), a OS process but not its libraries etc.
Start at a higher level and break apart Components as required. When populating the threat model it often becomes apparent that it would be easier to express an authentication method, or an asset storage location, or threat or control, if a specific sub-system was called out as a Component, so it’s unusual to get the list of Components complete on a first draft of a threat model, and the table becomes more complete as iterative work on the threat model happens.
Minimise infrastructure included as Components, especially if it makes sense for it to have its own threat model. It’s typical to not include supporting infrastructure that is otherwise transparent to the systems containing the business logic or performing related tasks. For instance we would not normally include; load balancers, proxies, DNS, etc., but if any of these implemented specific security controls for the system you may want to include them as Components (but often just listing them as a control in the Threats & Control Table is easier). The threats related to this supporting infrastructure is usually best handled in it’s own dedicated threat model (where the question ‘What is a Component?’ has a totally different answer), and usually it’s a threat model that can specifically focus on infrastructure threats, and becomes a companion threat model for application-level threat models.
Practically speaking, if there is any kind of design documentation for a system then the various systems defined in that make an ideal starting point for a list of Components.
Components Diagram
This should contain a diagram showing all, or at least the major components of the system being threat modelled. Use an existing diagram if you have one, even if it is is not perfect or completely up to date (the Components Details Table will be the accurate record of what the system comprises of). If you have to create a new diagram, keep it simple to begin with, just trying to capture the major components.
What is important for this diagram is that it shows which components connect to which other components. The actual data being sent is less important, so this diagram does not need to be a Data Flow Diagram (DFD). it’s purpose is to visually display connectivity. It can be helpful to group components by where they are deployed, but this is not required. Some teams like to colour code which components are in and out of scope.
Components Details Table
The Components Details Table must have a row per Component (and only one Component per row), and it contains the following columns:
- Name
A short unique name (amongst all names in this threat model) for the component. This is the name that will be used elsewhere in the threat model, so it must be unique, and for convienence it should be short. If the Component is something that has an identity, then using that identity is often a good idea, but it depends on how human friendly the name is.
- Location Stack
A comma separated list of the stack hosting the Component (the whole stack, up to the environment e.g. AWS account ID). This is useful for understanding what Components are co-located, and which are not, and this directly informs threats related to communications (e.g. transport security, authentication, authorization, etc.). For out-of-scope Components it’s usually fine to only capture if it is Internet hosted, or shares the same location as in-scope Components.
The threatware
measure
action relies on some standardised values here;3rd Party
,SaaS
,Internal Service
(but these are configurable inmeasure/measure_config.yaml
)- Purpose
A brief description of the purpose of the Component in relation to the system being threat modelled.
Common mistake
It’s a common mistake to state a Purpose
that is really generic and doesn’t help someone who is not familiar with the system to understand what the Component does. For instance for a Database
if you have a Purpose
of Stores data
then that doesn’t help, so it would be better to say Persistance storage for Service A that includes PII and password hashes
- In-scope
Values must be
Yes
orNo
(this can be localised). This is an important piece of information as threatware does not require threats to be listed for out-of-scope components. A system is in-scope if the team completing the threat model:created the component e.g. wrote the source code, or defined it via configuration
got the component provisioned (e.g a database), or got it on-boarded (e.g. a 3rd Party service or SaaS), and so are responsible for it
they own or operate the component e.g. hosting infrastructure like Kubernetes clusters, cloud accounts, 3rd party services
Tip
It’s better to have many small threat models rather than a few large threat models. Try to keep the number of components in-scope for a threat models to < 10 (and aim for ~5). The more in-scope components the larger the threat model becomes and more time it takes to complete.
- Tech Stack
Only populate for in-scope Components. A brief comma-separated list of technologies used to create the Component. For Components created by the team, usually language and framework is sufficient. For 3rd Party Components listing the provider is usually sufficient. This is useful as certain technologies give rise to specific threats, so detailing the stack allows a reviewer to decide if any relevant tech-specific threats should be added.
Components AuthN and AuthZ Table
The Components Authentication (AuthN) and Authorization (AuthZ) Table captures how each in-scope component authenticates and authorizes requests from each of the different identities that make requests to it.
Example
Imagine you have a client talking to a server, and the client has a user who authenticates to an Identity Provider via OAuth2, which results in a JWT that is passed to the Server on each request. Perhaps also the client can talk to the server with an anonymous user for some functionality. We would capture this with something like:
Components (In-scope) |
Identity |
Authentication |
Authorization |
---|---|---|---|
Server |
Authenticated Users |
JWT verified and validated |
Request evaluated against scopes in JWT |
Anonymous Users |
None |
Restricted to anonymous functionality |
Common mistake
What’s important to hightlight is that the entries are for requests coming into the Server and NOT how the Server authenticates to other components (this is a common misunderstanding).
This table has the following columns:
- Component (In-scope)
Must match the name from the Components Details Table. The name of the Component. An empty value means use the first non-empty value from a row above i.e. for multiple entries for a Component the Component name only needs to be given once.
- Identity
The Identity of the user/service/role etc., that initiates the request to the Component. This will be the Identity given to the principal making the request if authentication succeeds. It doesn’t matter which other Component the request actually comes from, all that matters is what Identity will this Component associate to the request. For each possible Identity that this Component understands, use a separate row.
- Authentication
How will the Component authenticate the request e.g. validate a JWT, compare to a password hash, cient certificate, delegate to another Component (i.e. trust the Identity value directly), etc. Also indicate where the value (i.e. identifier) used for the Identity will come from e.g. a scope in a JWT, username that was part of the request, etc. If the component does not do authentication, by convention the value
None
is used.
Tip
It can sometimes be difficult to decide if a Component is doing authentication or authorization. For instance, is an IP allowlist a form of authentication or a form of authorization? We’ll let the scholars decide, but in the mean time this guidance might be helpful:
Authentication will result in a Component assigning a request to a known and specific identifier for that Identity e.g. username, role, group.
Authorization is a decision about whether or not a request will be executed, and that decision can depend on many different factors, with one of the most common being the Identity assigned to the request.
So what about that IP allowlist? If it’s a set of specific IPs that are supposed to correspond to specific servers, then it’s probably authentication. If it’s a network range and any server in that range can make a request, then that doesn’t seems like an Identity is being assigned to the request, so it’s probably authorization.
- Authorization
How will the Component authorize the request e.g. check for a specific scope in the JWT, RBAC, delegate to IAM, look up permissions for a user/role, etc. If the component does not do authorization, by convention the value
None
is used. When no authorization is performed it means the Component will not place any restriction on whether it executes the request (notwithstanding it can still require an authenticated user).
Examples
The below are imaginary examples showing what might end up being in the AuthN/Z table. Do not copy these without doing the work to confirm they are appropriate for your system.
Component (In-scope) |
Identity |
Authentication |
Authorization |
---|---|---|---|
Web Application |
Users |
Login authentication delegated to supported |
Actions restricted to owned resources |
Database |
Web Application |
Username and password |
None |
S3 Bucket |
User |
Delegated to IAM via signed URL |
S3 bucket resource policy |
Operations |
Delegated to company SSO |
S3 Ops IAM Policy associated with Ops IAM Role |
|
API |
Client |
Login authentication via IdP |
JWT scopes |
Different Client |
API Key |
Permissions assigned to API key |
|
Internal Service |
Another Internal Service |
None |
Access delegated to K8 networking. |
Web service |
3rd party service |
mTLS client certificate |
3rd Party Role assigned to identity |
AWS Service |
My Service running in AWS |
Role access key from EC2 metadata |
Delegated to AWS IAM using policy. |
Kubernetes |
Microservice (as a Service Account) |
Delegated to Kubernetes admission controller |
Delegated to Kubernetes admission controller |
CSP Account |
Operations, Developers, Auditors |
Delegated to company SSO |
IAM Policy associated with IAM Role |
Firewall |
Network traffic |
None |
Source IP based ACLs |
Cron job |
n/a (doesn’t support incoming requests) |
n/a |
n/a |
Notepad.exe |
Developer |
None |
None |
sudo |
Local User |
Delegated to OS |
sudoers file |
Remote Workstation |
Remote user |
SSH Key |
OS permissions assigned to local group user is member of |
Local Filesystem |
Web Service |
Delegated to OS user authentication |
Delegated to OS file system ACLs |
By looking through the above examples you may already be able to infer the conventions of which information to provide for different Components in your own system, however the guidance below goes into more detail.
Guidance on populating AuthN and AuthZ Table
There is a huge range of possible authentication and authorization approaches in use today. That makes it hard to accurately and consistently capture how a Component authenticates and authorises requests coming into it. Below is some guidance on how to capture authentication and authorization information for a Component.
To begin, you need to populate the AuthN and AuthZ Table with all the in-scope Components from the Components Details Table. That would look something like:
Component (In-scope) |
Identity |
Authentication |
Authorization |
---|---|---|---|
ComponentA |
|||
ComponentB |
|||
ComponentC |
Now, taking each Component one at a time, we need to populate the Identity, Authentication and Authorization columns for that Component’s row.
Describe Identities
First we need to think about the identities that a Component knows about. You can add additonal rows for each identity the Component accepts requests from. Below is a flow-chart to help with determining what identities to add for a Component.
Having done this for one Component row you could have a table like:
Component (In-scope) |
Identity |
Authentication |
Authorization |
---|---|---|---|
ComponentA |
Users |
||
Admins |
|||
ComponentC |
|||
ComponentB |
|||
ComponentC |
FAQ
- Q: Why does the guidance say “Not including login” when considering different request types?
See Authentication FAQ question “Should I add a row for an ‘Anonymous’ caller, for requests made before they login?”
Describing Authentication
With a set of identities captured in different rows we now need to add authentication details to each row.
The goal is to capture the following details:
What authentication happens?
Who does the authentication?
What protocols/procedures for authentication are implemented?
What validation is performed (if relevant)?
With regards to ‘What authentication happens?’, sometimes there’s more than 1 type, and we should capture both:
Login Authentication. This can happen when a Component receives a long-lived credential as part of the request, and can map that credential to a known user. Not all Components handle login authentication themselves, some Components delegate this to other Components. The login process can involve multiple steps, but we aren’t looking to capture that complexity in this table. Often login authentication is only used in the initial request to a Component, and it returns a session token/identifier to be used with subsequent requests. Some common login authentication methods include:
Username and password e.g. HTTP’s ‘Basic Auth’,
Redirecting to an OAuth2/OIDC Identity Provider (IdP), either social (e.g. Apple, Twitch, GitHub, etc.), or enterprise (Okta, Google, Microsoft, etc.)
An API token given to a client and used on every request,
A client certificate used to establish a mutual-TLS connection
Session Authentication. This can happen when a Component receives a session token or identifier that they can use to determine the identity associated with a request. The session token/identifier is usually validated in some way before the associated identity is trusted. It’s possible there is no session authentication (i.e. the login authentication credentials are sent on every request e.g. HTTP ‘Basic Auth’). Some common session authentication methods include:
HTTP session cookies e.g. JSESSIONID
HTTP Authorization header with a JWT
OAuth2 access token
Continuing on from the flow chart for describing identity (the output of the below flow chart should be captured in the Authentication column for the corresponding Identity).
It’s also a good idea to link to more information in the description provided, if possible.
Having captured authentication for the various identities known to this Component, you could have a table like:
Component (In-scope) |
Identity |
Authentication |
Authorization |
---|---|---|---|
ComponentA |
Users |
Login authentication via user name and password. |
|
Admins |
Login authentication delegated to Corporate SSO. |
||
ComponentC |
None |
||
ComponentB |
|||
ComponentC |
Hint
A common security issue is discovering that different Components in a system are applying different levels of validation when they authenticate a certain identity type.
FAQ
- Q: What exactly does ‘delegated’ mean for authentication?
Delegated means that the Component relies on another component to:
validate the authentication credential supplied (e.g. validate JWT signature), or
map the authentication credential to a specific Identity (e.g. username/password to account ID, lookup a session token etc.).
- Q: Should I add a row for an ‘Anonymous’ caller, for requests made before they login?
You should only capture “Anonymous” callers to a Component if the Component is designed to take anonymous traffic (and we do not count the initial anonymous request for a user to login, that is just assumed). So a website Component that is publicly browsable could have a row for an “Anonymous” Identity. However, the same website that forces a user to immediately login wouldn’t have an “Anonymous” row. It is better to just capture Identities that the Component is expecting to deal with. Otherwise every Component would have to have an “Anonymous” Identity row because every Component that can receive network traffic could in-theory recieve an anonymous request, but since it’s always true it’s just noise in the threat model document.
- Q: The authentication protocol my Component uses involves multiple stages and multiple flows, do I need to capture this process as multiple rows?
No. The purpose of the row is not capture detail on the authentication process (e.g. every OAuth2 redirect), but rather to capture the methods and protocols used for authentication, by the Component, for the different types of callers (i.e. identities) the Component receives, and any relevant validation done to confirm those identities. Detailed information about authentication protocols can be captured (if beneficial) in the Diagrams section.
- Q: How do I decide whether an authentication method is ‘login’ or ‘session’?
A classic login authentication method is username and password. However many systems enforce password expiry, meaning the password is only valid for a period of time. So while a ‘session’ implies something valid for a period of time, ‘time’ is not the only factor to use to distinguish between login and session authentication, because passwords can be time limited, but are clearly not a session authentication method (the same applies to API keys and client certificates - in fact there should be few if any ‘permanent’ credentials). In practice, a session token will usually also have a significantly shorter lifetime than login credentials (and so be wary of anyone trying to minimise security controls for so-called “session tokens” that live as long as login credentials).
Another factor to identify a session authentication method is whether the authentication information in the request used was obtained by the exchange of other credentials (i.e. login credentials). This is still not definitive though, as some authentication methods effectively exchange login credentials for other login credentials e.g. OAuth2 refresh token.
- Q: What does “None” mean?
None can mean different things:
Requests to the Component are unauthenticated. The Component does not know who the caller is, it does not associate any identity with the caller.
Even when another Component in the request path before this Component does authenticate the caller, if that identity is NOT passed to this Component, then “None” is still the approriate answer. Whilst the larger system may have authenticated the caller, this component does not know the identity of the caller, and moreover an appropriately positioned attacker in the system can make arbitrary calls to the Component.
If the Component is sent an actual Identity but never consumes/processes that Identity (e.g. it’s set in an HTTP header the Component doesn’t read), then “None” is still the approriate answer. In this case not consuming/processing that Identity makes it equivlant to not knowing it in the first place.
If the Identity is logged then that’s relevant from a repudiation perspective, but not an authentication perspective.