The system-level network case study models the behavior of changing network infrastructure with respect to a constant set of access control requirements. The example is motivated by the problem of moving a portable computer from one network to another, seamlessly maintaining functionality without violating security constraints. Figure 20.1 illustrates the problem where in a portable computer is carried between an office environment that is closed and secure, a home environment that is somewhat controlled, and a completely open environment such as a coffee shop, where the environment is not known or controllable. The user of the portable computer would like to work on the same task while satisfying security requirements. In effect, a trade-off between accessibility and access control is continually being made.
This case study differs substantially from the RTL and power-aware studies in that the system implementation is not a part of the modeling process. Certainly architectural issues are addressed, but the biggest issue is developing a reasonable requirements model. Thus, we will not develop models of firewalls and routers, but models of working environments and resource access restrictions.
Although models may differ, the methodology followed by the networking case study is virtually identical to that in the previous case studies. First, decide what information must be known. Second, select domains for the basic models that are as abstract as possible. Then define basic models for components and compose models to define systems. Finally, generate analysis models from composite models by moving them to different domains.
There are three types of models involved in this example, responsible for representing function, performance requirements, and operational infrastructure. The functional requirements model defines what services the network must provide to the user and what services are available. The security model defines access control requirements on various system resources and specifies constraints on how functions are provided. Finally, the infrastructure models define support provided by the various network infrastructures. If the infrastructure can provide required services while meeting system requirements, then we have an ideal system. However, modeling what the system can provide is as important as providing a binary correctness result.
Access requirements can be viewed as invariants on system state. Specifically, no network entity that is not trusted should be allowed to access private resources. Because requirements define invariants, the static
domain is the most appropriate domain for an abstract specification. Functionality and infrastructure models differ from access requirements in that a need exists to specify temporal requirements. When a request is processed by the infrastructure, it must respond to that request by changing what it knows about its resources. Likewise, a requesting entity changes state when its requests are met or denied.
Because there are no timing requirements specified, the most appropriate domain here is the state_based
domain, where change can be represented, but the details of that change can be avoided. The finite_state
and infinite_state
domains may also be appropriate, but nothing is known about the cardinality of the state set. Therefore, infrastructure and functional requirements models are written in the state_based
domain.
The network_entities
package defined in Figure 20.2 defines a collection of shared types, functions, and constants that are shared across specifications. Virtually any large specification will define a package like network_entities
that provides shared constructs to all models. A similar package can be found in the RTL case study in Chapter 17.
Example 20.2. Basic resource and credential entities.
package network_entities()::static is id ::type is constant; resource ::type is constant; secure_server :: resource is constant; public_server :: resource is constant; internal :: id is constant; external :: id is constant; public :: id is constant; private :: id is constant; firewall :: id is constant; message ::type is data request(a,b::id;r::resource)::request? | grant(a,b::id;r::resource)::grant? | revoke(a,b::id;r::resource)::revoke? | trust(a,b::id)::trust? | distrust(a,b::id)::distrust? end data; trusts_with(a,b::id;r::resource)::boolean; trusts(a,b::id)::boolean; grants(a,b::id; r::resource)::boolean; transitive_trust(a,b,c::id)::boolean is trusts(a,b) and trusts(b,c) => trusts(a,c); transitive_trusts_with(a,b::id; r::resource)::boolean is trusts(a,b) and trusts_with(b,c,r) => trusts_with(a,c,r) will_grant(a,b::id; r::resource)::boolean is trusts_with(a,b,r)and requests(a,b,r) => grants(a,b,r) end package network_entities;
The id
and resource
types define uninterpreted types for entities in the network and resources they may request. These types are uninterpreted because their internal representation is not germane to what is being specified. It is sufficient to define properties for entities and resources in the specification.
Several constants are defined of type resource
and id
that are visible to all specifications using the package. The two server declarations represent resources that may be requested and are controlled by the secure infrastructure. The two id
declarations define entities that are known in all specifications. Again, the details of entities and resources are not important, thus they are declared as constants without specific values.
The message
data type defines messages that are exchanged between network entities. It is critical that these definitions exist in a shared package, because all component specifications must be able to send and receive messages for requests and updates to trust relationships. Specific message instances exist for requesting, granting, and revoking access as well as for establishing and removing trust relationships.
The final element of the network_entities
package defines a collection of predicates that define properties of components that grant access to resources. The declarations of trusts_with
, trusts
, and grants
define relations between entities and resources: trusts_with
is true if one entity trusts another with specific resource; trusts
is true if one entity trusts the assertions of another.
The transitive_trusts
, transitive_trust
, and will_grant
predicates define how trust is established and the conditions for granting access to a resource. First, transitive_trust
simply defines and names a transitive property for trust relationships; transitive_trusts_with
defines a relation such that if a
trusts b
and b
trusts c
with r
, then a
trusts c
with r
. Finally, will_grant
defines a property asserting when one entity will grant access to a resource.
The way these predicates establish trust and grant resource access is relatively simple. If a resource r
is requested from m
by n
, and m
trusts n
with that resource, access is granted. Establishing that m
trusts n
with the resource may result from first-hand knowledge. However, if m
trusts an entity p
that trusts n
with r
, then m
also trusts n
with r
. Trust is transitive, allowing the formation of chains from an initial trusted source. This is an exceptionally naive trust model, but will suffice for this case study.
Note that none of the defined properties is asserted in the package. The properties give names to relationships, but do not assert those relationships for any particular entities. For the properties to hold, they must be asserted as terms in models that use the network_entities
package.
The access control requirements model defines a collection of security constraints that the network infrastructure must satisfy while providing services. It defines system-level constraints on the function of every infrastructure element in the network. While the functional requirements define what the network must do, access control requirements define what the network must not do. In this sense, access control requirements define safety conditions.
Figure 20.3 defines the access control requirements model. A component representation is chosen over a facet representation to allow inclusion of usage assumptions and correctness conditions. Usage assumptions explicitly document assumptions made when defining requirements. In effect, they define conditions that the user must accept or satisfy to use this system. Implications define correctness conditions over definitions. Thus, they support specification of theorems that should hold as a result of assumptions and definitions. If the theorems do not hold, then something about the specified requirements is incorrect.
Example 20.3. Basic access control requirements model.
use network_entities(); component networkSecurity()::static is begin assumptions forall(a::id | trusts(a,trusted_id)); end assumptions; definitions end definitions; implications // if a grants b r, then a must trust b with r (forall a::id | (forall b::id | (forall r::resource | grants(a,b,r) => trusts_with(a,b,r)))); end implications; end component networkSecurity;
The only assumption made is that every entity trusts trusted_id
. This trusted entity forms the root of all trust relationships in this system. It also serves as a potential single point of failure. If trusted_id
is compromised, then the entire network may fail. Making the assumption that all entities trust trusted_id
prevents this case from occurring in this analysis. In essence, the assumption is that trusted_id
will never be untrusted.
The requirements model is quite simple and asserts only that if a resource is granted to an entity, then the granter must continue to trust the grantee with the resource as long as the grant is valid. If grants(a, b, r)
holds, then trusts_with(a, b, r)
must also hold or requirements are violated. If grants(a, b, r)
does not hold, then the implication is immediately true. The static
domain is used because there is no need to represent change, as requirements will be defined as an invariant that must hold in all states. Note that if trusts_with(a, b, r)
holds, it is not necessary for a
to grant r
to b
.
Note that the requirements model uses an implication to assert its correctness condition, rather than making it a requirement. This is a stylistic choice concerning the way errors will discovered. Making the condition an implication asserts that the condition must be provable from requirements. Thus, if requirements violate the condition, the inconsistency will be detected when the condition is checked. If the condition were asserted as a requirement, the inconsistency exists in the specification immediately and can be detected without checking the implication. Unfortunately, this implies that the inconsistency could be used to verify other conditions if it is not recognized. For this reason, an implication is used rather than a definition term.
Modeling constraints outside the context of required functionality is not useful. For example, it is quite possible to define a secure network by simply not providing connectivity. If no resources are accessed and no resources are available, the network meets ideal security requirements. Of course, this is not desirable — a network without connectivity does not meet even its basic functional requirements. Thus the functional requirements model defines a minimal set of system requirements, that must be achieved in the content of constraints.
The functional requirements model defines desired functionality from the perspective of the user. It is parameterized over the identity of the sender, allowing the analysis of requirements from the perspective of a sender, behind the firewall or outside the firewall. Specifically, the send_as
design parameter can be internal
, external
, or another id
that indicates where the requester is. Thus, correctness analysis can be performed from several perspectives. Figure 20.4 defines resources that the portable computer user would ideally like to have available.
Example 20.4. Basic functional requirements.
use network_entities(); component networkFunction (send_as::design id; i::input message; o::output message)::state_based is eventually(f::boolean)::boolean is not (f)implies eventually(f'), begin assumptions end assumptions; definitions eventually(o=request(send_as,private,private_server)); eventually(o=request(send_as,private,public_server)); end definitions; implications case send_as of {internal} -> eventually(i=grant(private)) and eventually(i=grant(public)) {external} -> eventually(i=grant(public)) end case; end implications; end facet networkFunction;
Having declarations of entities and resources, and a requirements model defining system requirements, network infrastructure models must be defined. Specifically, an internal infrastructure representing the work environment, an external infrastructure representing the home and public infrastructure, and a firewall environment represent the firewall behavior will now be defined.
Each infrastructure model defines various implementations of functionality that must satisfy system-level constraints. Thus, infrastructure models must satisfy access control requirements while defining their own requirements. This will be modeled by defining trust properties for each network environment and then conjuncting system-level requirements with each environment model.
The private infrastructure defined in Figure 20.5 represents the network environment within the organization that controls computing resources private_server
, and public_server
, defined earlier. It represents the world behind the organization’s firewall. All infrastructure models use the state_based
domain because trust relationships must change. The static
domain is more appropriate for requirements because they cannot change. In effect, they define invariants on the state of network infrastructure elements.
Example 20.5. Private network infrastructure.
use network_entities(); component networkPrivateInfrastructure (i::input message; o::output message; self::design id)::state_based is begin assumptions trusted_firewall: trusts(self,firewall) and forall(x:id | trusts(firewall,x) => trusts_with(firewall,x,private_server)); trusted_internal: trusts_with(self,internal,private_server) and trusts_with(self,internal,public_server); trusted_external: trusts_with(self,external,public_server); end assumptions; definitions // incoming request request_incoming: request?(i) and a(i)=self => if trusts_with(self,b(i),r(i)) then o'=grant(self,b(i),r(i)) and grants(self,b(i),r(i))' else o'=revoke(self,b(i),r(i)) and not(grants(self,b(i),r(i)))' end if; // incoming trust and distrust. trust_incoming: trust?(i) => trusts(a(i),b(i))'=true; distrust_incoming: distrust?(i) and trusts(a(i),b(i)) => trusts(a(i),b(i))'=false; trust_maintenance: trusts(a,b) and not(i=distrust(a,b)) => trusts(a,b)'=true; // maintain grants grant_maintenance: if grants(self,b,r) and not(trusts_with(self,b,r)) then o'=revoke(self,b,r) and not(grants(self,b,r))' else grants(self,b,r)' end if; end definitions; implications end implications; end component networkPrivateInfrastructure;
The private infrastructure makes three trust assumptions: (i) it trusts the firewall and it trusts who the firewall trusts with the private server, (ii) it trusts internal entities with both servers, and (iii) it trusts external entities with only the public server. These three assumptions allow it to grant access to both servers to internal requesters and only the public server to external requesters. The firewall is trusted, thus the firewall can act as an arbiter for establishing trust.
The requirements model consists of five terms that represent processing incoming requests, processing incoming trust assertions and de-assertions, and maintaining trust and existing resource grants over state change. The first, request_incoming
, processes an incoming resource request. If the node trusts the requester with the resource, a grant message is issued and the grant is maintained locally. If the node does not trust the requester, the grant is revoked and the local record of the grant is negated. It is here that we first see the use of state_based
to move from one system state to the next, based on grants and revocations.
The trust_incoming
and distrust_incoming
terms are similar to the request_incoming
term. They react to a specific input message and revise trust relations in the next state. The trust_maintenance
term defines when trust is maintained over state change. When an entity is trusted in the current state, it is trusted in the next state unless a message has arrived that revokes that trust.
Finally, the grant_maintenance
term maintains valid resource grants from one state to the next. It states that if the trust chain is broken and cannot be re-established, then the grant is revoked. Otherwise, the grant is kept in place in the next state. The system is thus required to recheck trust and grant relationships each time the system changes state.
Example 20.1. Internal Request for the Private Server
As an example of how the infrastructure models operate, we can examine the scenario where an internal agent requests the private server. To initiate the request, the following message arrives at the private infrastructure’s input:
request(private, internal, private_server)
Assuming that self=private
, the message will cause the request_incoming
term to be instantiated as follows:
request_incoming: true and self=self => if trusts_with(self, internal, private_server) then o'=grant(self,internal,private_server) and grants(self, internal, private_server)' else o' =revoke(self,internal,private_server) and not(grants(self, internal, private_server))' end if;
The implication condition is true and the assumption trusted_internal
makes the if
condition true. Therefore the private infrastructure sends the message:
grant(private, internal, private_server)
and asserts:
grants(self, internal, private_server)
in the next state.
Example 20.2. External Request for the Private Server
As an example of how the infrastructure models operate, we can examine the scenario where an external agent requests the private server. To initiate the request, the following message arrives at the private infrastructure’s input:
request(private,external,private_server)
Assuming that self=private
, the message will cause the request_incoming
term to be instantiated as follows:
request_incoming: true and self=self => if trusts_with(self, external, private_server) then o'=grant(self,external,private_server) and grants(self,external, private_server)' else o' =revoke(self,external,private_server) and not(grants(self, external, private_server))' end if;
The implication condition is true. However, the if
condition cannot be verified — it is neither true nor false. Therefore, no message is sent and nothing is asserted in the next state. The request is ignored. If the internal infrastructure knew it did not trust the external requester, then a revoke message would have been sent.
Example 20.3. Establishing Trust with an External Requester
As a final example of the infrastructure at work, assume that an external request for the private server is made and the firewall trusts the external requester. This scenario is modeled by the following two messages:
trust(firewall,external) request(private,external,private_server)
The first message is processed by process_trust
and establishes that firewall
trusts external
. Combined with the assumption that private
trusts firewall
, it can be established that private
trusts external
. This would represent a situation where a VPN link is established between the firewall and the external requester.
Now the request message is processed as in the previous example. The distinction is that now it can be established that the external requester can be trusted with the private server: private
trusts firewall
and trusts anyone firewall
trusts with private_server
. The if
condition is thus satisfied and the grant is processed as it was in the first example.
The firewall
component in Figure 20.6 is nearly identical to the networkPrivateInfrastructure
model, with the exception that it assumes initial trust only in the private infrastructure. It does not trust the external infrastructure in any way initially. Incoming requests are handled in exactly the same manner, except that initially the firewall controls no resources. Thus, requests for resources to the firewall will go unanswered. The firewall is perfectly capable of establishing trust with other agents — its primary function.
Example 20.6. Firewall infrastructure.
use network_entities(); component firewall (i::input message; o::output message; self::design id)::state_based is begin assumptions trusted_firewall: trusts(self,private); end assumptions; requirements // incoming request request_incoming: request?(i) and a(i)=self => if trusts_with(self,b(i),r(i)) then o'=grant(self,b(i),r(i)) and grants(self,b(i),r(i))' else o'=revoke(self,b(i),r(i)) and not(grants(self,b(i),r(i)))' end if; // incoming trust and distrust. Can assert trust between // any nodes trust_incoming: trust?(i) => trusts(a(i),b(i))'=true; distrust_incoming: distrust?(i) and trusts(a(i),b(i)) => trusts(a(i),b(i))'=false; trust_maintenance: trusts(a,b) and not(i=distrust(a,b)) => trusts(a,b)'=true; // maintain grants grant_maintenance:if grants(self,b,r) and not(trusts_with(self,b,r)) then o'=revoke(self,b,r)and not(grants(self,b,r))' else grants(self,b,r)' end if; end requirements; implications end implications; end component firewall;
The public infrastructure defined in Figure 20.7 differs from the private infrastructure in that it trusts everyone, but it has no resources to grant. Thus, the trust relationships are not particularly useful.
Example 20.7. Public network infrastructure.
use network_entities(); component networkPublicInfrastructure (i::input message; o::output message; self:: design id)::state_based is begin assumptions trusts_all: forall(x::id | trusts(self,x)); end assumptions; definitions // incoming request request_incoming: request?(i) and a(i)=self => if trusts_with(self,b(i),r(i)) then o'=grant(self,b(i),r(i)) and grants(self,b(i),r(i))' else o'=revoke(self,b(i),r(i)) and not(grants(self,b(i),r(i)))' end if; // incoming trust and distrust. trust_incoming: trust?(i) => trusts(a(i),b(i))'=true; distrust_incoming: distrust?(i) and trusts(a(i),b(i)) => trusts(a(i),b(i))'=false; trust_maintenance: trusts(a,b) and not(i=distrust(a,b)) => trusts(a,b)'=true; // maintain grants grant_maintenance: if grants(self,b,r) and not(trusts_with(self,b,r)) then o'=revoke(self,b,r) and not(grants(self,b,r))' else grants(self,b,r)' end if; end definitions; implications end implications; end component networkPublicInfrastructure;
Figure 20.8 defines the composition of the functional requirements model, the access control requirements model, and either the public or private infrastructure models. The parameter public
selects the infrastructure model. Here inInfrastructure
(true
) composes the network functionality and security constraint swith the public infrastructure model. In contrast,inInfrastructure
(false
) composes the network functionality and security constraints with the private infrastructure model. Recall that the requirements model defines different requirements based on the location of the requesting entity.
Example 20.8. Composite system model.
use network_entities(); use static_state_based(); facet inInfrastructure(public:: design boolean) :: state_based is m::message; begin infra: if public=true then networkFunction(external,m,m) else networkFunction(internal,m,m) end if; public: networkPublicInfrastructure(m,m,public); firewall: firewall (m,m,firewall); private: networkPrivateInfrastructure(m,m,private) * static_state_based.gamma(networkSecurity()); end facet inInfrastructure;
The default interaction between state_based
and static
domains moves the requirements specification to the state_based
domain. It is then composed with the private infrastructure model to define constraints on how resources are accessed. In effect, the requirements model defines a state invariant over all states. These requirements behave like assertions, monitoring the behavior of the infrastructure.
What this specification does not concern itself with is just as important — potentially more important than what is specified. No attempt is made to coordinate messages on m
, as this is outside the scope of the analysis being performed. No attempt is made to model timing issues or to model how authentication is performed. This model deals quite simply with the establishment and maintenance of trust relationships and their effects on resource access. No attempt is made to deal with implementation issues. As such, this is purely a requirements model.
The inInfrastructure
model defines a configurable model that defines and composes functional models, constraints, and implementation infrastructure. Analysis of these models can be achieved using a number of techniques by moving the inInfrastructure
model to a domain that supports analysis. The state_based
model can be moved to a simulation domain, much like previous examples. However, this would require refinement to the discrete_time
domain. More appropriate here is analysis involving model checking or theorem proving, due to the logical nature of the specifications.
Constructing the analysis model uses the same type of functor as for the power-aware example. Here, a state_based
model is moved to a model-checking domain for analysis:
state_based_mc.gamma(inInfrastructure(true));
The resulting model can be analyzed using model-checking techniques to assure that the invariant is met in each state and to ensure that functional requirements involving sequences of states are satisfied.
This case study represents a significant departure from digital system design and looks exclusively at requirements analysis. Previous studies examine the impacts of local design decisions on system-level behavior. Here, exploring system requirements without consideration for implementation technologies is the objective. Regardless, the impacts of local design decisions are explored in the context of system requirements.
The local design decisions explored in this case study are requirements placed on different network environments and components. Access control requirements can be varied, trust models can be altered or changed, and even models for establishing trust in the system can be changed. Here the local decisions are not implementation related but are instead requirements related.
What this case study demonstrates is a capability for modeling requirements sets before an implementation direction is chosen. System design folklore suggests that the cost of fixing an error increases by an order of magnitude for each design cycle entered prior to discovery of the error. The only thing questionable about this approximation is whether the cost should increase by only a single order of magnitude. Thus, the value of exploring requirements to detect errors and optimize requirements prior to system implementation cannot be overstated.