Package: vakt
Attribute-based access control (ABAC) SDK for Python.DocumentationDocumentationDescriptionConceptsInstallUsageComponentsPolicyInquiryRulesComparison-relatedLogic-relatedList-relatedNetwork-relatedString-relatedInquiry-relatedCheckerGuardStorageMemoryMongoDBSQLRedisMigrationCachingCachingRegexCheckerCaching the entire Storage backendCaching the GuardJSONLoggingAuditMilestonesBenchmarkAcknowledgementsDevelopmentLicenseDescriptionVakt is an attribute-based and policy-based access control (ABAC)
toolkit that is based on policies.
ABAC stands aside of RBAC and ACL models, giving you
a fine-grained control on definition of the rules that restrict an access to resources and is generally considered a
"next generation" authorization model.
In its form Vakt resemblesIAM Policies, but
has a way nicer attribute managing.Seeconceptssection for more details.Back to topConceptsGiven you have some set of resources, you can define a number of policies that will describe access to them
answering the following questions:What resources (resource) are being requested?Who is requesting the resource?What actions (action) are requested to be done on the asked resources?What are the rules that should be satisfied in the context of the request itself?What is resulting effect of the answer on the above questions?The overall diagram ofvaktworkflow is:Vakt allows you to gain:Policy Based Access Control(vakt is based on Policies that describe access rules, strategies to your resources)Fine-Grained Authorization(vakt Policies give you fine-grained control over resource's, subject's, action's and context's attributes)Dynamic Authorization Management(you can add Policies and change their attributes)Externalized Authorization Management(you can build own external AuthZ server with vakt, see examples)Back to topInstallVakt runs on Python >= 3.6.
PyPy implementation is supported as well.Bare-bones installation with in-memory storage:pipinstallvaktFor MongoDB storage:pipinstallvakt[mongo]For SQL storage:pipinstallvakt[sql]pipinstall$ANY_DB_DRIVER_OF_YOUR_CHOICE_SUPPORTED_BY_SQLALCHEMYFor Redis storage:pipinstallvakt[redis]Also seeredis-pydocs.
For example ifhiredisis found in the system, it will be used as a faster parser.
However vakt doesn't enforce this dependency.Back to topUsageA quick dive-in:importvaktfromvakt.rulesimportEq,Any,StartsWith,And,Greater,Lesspolicy=vakt.Policy(123456,actions=[Eq('fork'),Eq('clone')],resources=[StartsWith('repos/Google',ci=True)],subjects=[{'name':Any(),'stars':And(Greater(50),Less(999))}],effect=vakt.ALLOW_ACCESS,context={'referer':Eq('https://github.com')},description="""Allow to fork or clone any Google repository forusers that have > 50 and < 999 stars and came from Github""")storage=vakt.MemoryStorage()storage.add(policy)guard=vakt.Guard(storage,vakt.RulesChecker())inq=vakt.Inquiry(action='fork',resource='repos/google/tensorflow',subject={'name':'larry','stars':80},context={'referer':'https://github.com'})assertguard.is_allowed(inq)Or if you prefer Amazon IAM Policies style:importvaktfromvakt.rulesimportCIDRpolicy=vakt.Policy(123457,effect=vakt.ALLOW_ACCESS,subjects=[r'<[a-zA-Z]+ M[a-z]+>'],resources=['library:books:<.+>','office:magazines:<.+>'],actions=['read','get'],context={'ip':CIDR('192.168.0.0/24'),},description="""Allow all readers of the book library whose surnames start with M get and read any book or magazine,but only when they connect from local library's computer""",)storage=vakt.MemoryStorage()storage.add(policy)guard=vakt.Guard(storage,vakt.RegexChecker())inq=vakt.Inquiry(action='read',resource='library:books:Hobbit',subject='Jim Morrison',context={'ip':'192.168.0.220'})assertguard.is_allowed(inq)For more examples seehere.Back to topComponentsPolicyPolicy is a main object for defining rules for accessing resources.
The main parts reflect questions described inConceptssection:resources - a list of resources. Answers: what is asked?subjects  - a list of subjects. Answers: who asks access to resources?actions - a list of actions. Answers: what actions are asked to be performed on resources?context - rules that should be satisfied by the given inquiry's context.effect - If policy matches all the above conditions, what effect does it imply?
Can be eithervakt.ALLOW_ACCESSorvakt.DENY_ACCESSAllresources,subjectsandactionsare described with
a list containing strings, regexes,Rulesor dictionaries of strings (attributes) toRules.
Each element in list acts as logical OR. Each key in a dictionary of Rules acts as logical AND.contextcan be described only with a dictionary ofRules.Depending on a wayresources,subjects,actionsare described, Policy can have either
String-based or Rule-based type. Can be inspected bypolicy.type.
This enforces the use of a concrete Checker implementation. SeeCheckerfor more.fromvaktimportPolicy,ALLOW_ACCESSfromvakt.rulesimportCIDR,Any,Eq,NotEq,In# Rule-based policy (defined with Rules and dictionaries of Rules)Policy(1,description="""Allow access to administration interface subcategories: 'panel', 'switch' if user is nota developer and came from local IP address.""",actions=[Any()],resources=[{'category':Eq('administration'),'sub':In(['panel','switch'])}],subjects=[{'name':Any(),'role':NotEq('developer')}],effect=ALLOW_ACCESS,context={'ip':CIDR('127.0.0.1/32')})# String-based policy (defined with regular expressions)Policy(2,description="""Allow all readers of the book library whose surnames start with M get and read any book or magazine,but only when they connect from local library's computer""",effect=ALLOW_ACCESS,subjects=['<[\w]+ M[\w]+>'],resources=('library:books:<.+>','office:magazines:<.+>'),actions=['<read|get>'],context={'ip':CIDR('192.168.2.0/24')})Basically you want to create some set of Policies that encompass access rules for your domain and store them for
making future decisions by theGuardcomponent.st=MemoryStorage()forpinpolicies:st.add(p)Additionally you can create Policies with predefined effect classes:fromvaktimportPolicyAllow,PolicyDeny,ALLOW_ACCESS,DENY_ACCESSp=PolicyAllow(1,actions=['<read|get>'],resources=['library:books:<.+>'],subjects=['<[\w]+ M[\w]+>'])assertALLOW_ACCESS==p.effectp=PolicyDeny(2,actions=['<read|get>'],resources=['library:books:<.+>'],subjects=['<[\w]+ M[\w]+>'])assertDENY_ACCESS==p.effectBack to topInquiryInquiry is an object that serves as a mediator between Vakt and outer world request for resource access. All you need
to do is take any kind of incoming request (REST request, SOAP, etc.) and build anInquiryout of it in order to
feed it to Vakt. There are no concrete builders for Inquiry from various request types, since it's a very meticulous
process and you have hands on control for doing it by yourself. Let's see an example:fromvaktimportInquiryfromflaskimportrequest,session...# if policies are defined on some subject's and resource's attributes with dictionaries of Rules:inquiry2=Inquiry(subject={'login':request.form['username'],'role':request.form['user_role']},action=request.form['action'],resource={'book':session.get('book'),'chapter':request.form['chapter']},context={'ip':request.remote_addr})# if policies are defined with strings or regular expressions:inquiry=Inquiry(subject=request.form['username'],action=request.form['action'],resource=request.form['page'],context={'ip':request.remote_addr})Here we are taking form params from Flask request and additional request information. Then we transform them
to Inquiry. That's it.Inquiry has several constructor arguments:resource - any | dictionary of str -> any. What resource is being asked to be accessed?action - any | dictionary str -> any. What is being asked to be done on the resource?subject - any | dictionary str -> any. Who asks for it?context - dictionary str -> any. What is the context of the request?If you were observant enough you might have noticed that Inquiry resembles Policy, where Policy describes multiple
variants of resource access from the owner side and Inquiry describes an concrete access scenario from consumer side.Back to topRulesRules allow you to describe conditions directly onaction,subject,resourceandcontextor on their attributes.
If at least one Rule in the Rule-set is not satisfied Inquiry is rejected by given Policy.Attaching a Rule-set to a Policy is simple. Here are some examples:fromvaktimportPolicy,rulesPolicy(...,subjects=[{'name':rules.Eq('Tommy')}],),Policy(...,actions=[rules.Eq('get'),rules.Eq('list'),rules.Eq('read')],),Policy(...,context={'secret':rules.string.Equal('.KIMZihH0gsrc'),'ip':rules.net.CIDR('192.168.0.15/24')},)There are a number of different Rule types, see below.If the existing Rules are not enough for you, feel free to define yourown.Comparison-relatedRuleExample in PolicyExample in InquiryNotesEq'age': Eq(40)'age': 40NotEq'age': NotEq(40)'age': 40Greater'height': Greater(6.2)'height': 5.8Less'height': Less(6.2)'height': 5.8GreaterOrEqual'stars': GreaterOrEqual(300)'stars': 77LessOrEqual'stars': LessOrEqual(300)'stars': 300Logic-relatedRuleExample in PolicyExample in InquiryNotesTruthy'admin': Truthy()'admin': user.is_admin()Evaluates on Inquiry creationFalsy'admin': Falsy()'admin': lambda x: x.is_admin()Evaluates on Inquiry creationNot'age': Not(Greater(90))'age': 40And'stars': And(Greater(50), Less(89))'stars': 78Also, attributes in dictionary of Rules act as AND logicOr'stars': Or(Greater(50), Less(120), Eq(8888))'stars': 78Also, rules in a list of, say,actionsact as OR logicAnyactions=[Any()]action='get',action='foo'Placeholder that fits any valueNeithersubjects=[Neither()]subject='Max',subject='Joe'Not very useful, left only as a counterpart of AnyList-relatedRuleExample in PolicyExample in InquiryNotesIn'method': In('get', 'post')'method': 'get'NotIn'method': NotIn('get', 'post')'method': 'get'AllIn'name': AllIn('Max', 'Joe')'name': ['Max', 'Joe']AllNotIn'name': AllNotIn('Max', 'Joe')'name': ['Max', 'Joe']AnyIn'height': AnyIn(5.9, 7.5, 4.9)'height': [7.55]AnyNotIn'height': AnyNotIn(5.9, 7.5, 4.9)'height': [7.55]Network-relatedRuleExample in PolicyExample in InquiryNotesCIDR'ip': CIDR('192.168.2.0/24')'ip': 192.168.2.4String-relatedRuleExample in PolicyExample in InquiryNotesEqual'name': Equal('max', ci=True)'name': 'Max'Aliased asStrEqual. Use instead ofEqit you want string-type check and case-insensitivityPairsEqual'names': PairsEqual()'names': ['Bob', 'Bob']Aliased asStrPairsEqualRegexMatch'file': RegexMatch(r'\.rb$')'file': 'test.rb'StartsWith'file': StartsWith('logs-')'file': 'logs-data-101967.log'Supports case-insensitivityEndsWith'file': EndsWith('.log')'file': 'logs-data-101967.log'Supports case-insensitivityContains'file': Contains('sun')'file': 'observations-sunny-days.csv'Supports case-insensitivityInquiry-relatedInquiry-related rules are useful if you want to express equality relation between inquiry elements or their attributes.RuleExample in PolicyExample in InquiryNotesSubjectMatchresources=[{'id': SubjectMatch()}]Inquiry(subject='Max', resource={'id': 'Max'})Works for the whole subject value or one of its attributesActionMatchsubjects=[ActionMatch('id')]Inquiry(subject='Max', action={'method': 'get', id': 'Max'})Works for the whole action value or one of its attributesResourceMatchsubjects=[ResourceMatch('id')]Inquiry(subject='Max', resource={'res': 'book', id': 'Max'})Works for the whole resource value or one of its attributesSubjectEqual'data': SubjectEqual()Inquiry(subject='Max')Works only for strings. Favor SubjectMatchActionEqual'data': ActionEqual()Inquiry(action='get')Works only for strings. Favor ActionMatchResourceIn'data': ResourceIn()Inquiry(resource='/books/')Works only for strings. Favor ResourceMatchBack to topCheckerChecker allows you to check whether Policy matches Inquiry by concrete field (subject,action, etc.). It's used
internally byGuard, but you should be aware of Checker types:RulesChecker - universal type that is used to check match of Policies defined with Rules or dictionaries of Rules
(Rule-based Policy type). It gives you the highest flexibility.
Most of the time you will use this type of Polices and thus this type of a Checker.
Besides, it's much more performant than RegexChecker. Seebenchmarkfor more details.fromvaktimportRulesCheckerch=RulesChecker()# etc.RegexChecker - checks match by regex test for policies defined with strings and regexps (String-based Policy type).
This means that all you Policies
can be defined in regex syntax (but if no regex defined in Policy falls back to simple string equality test) - it
gives you better flexibility compared to simple strings, but carries a burden of relatively slow performance.
You can configure a LRU cache size to adjust performance to your needs:fromvaktimportRegexCheckerch=RegexChecker(2048)ch2=RegexChecker(512)# etc.Seebenchmarkfor more details.Syntax for description of Policy fields is:'<foo.*>'
 'foo<[abc]{2}>bar'
 'foo<\w+>'
 'foo'Where<>are delimiters of a regular expression boundaries part. Custom Policy can redefine them by overridingstart_tagandend_tagproperties. Generally you always want to use the first variant:<foo.*>.Due to relatively slow performance of regular expressions execution we recommend to define your policies in
regex syntax only when you really need it, in other cases use simple strings:
both will work perfectly (and now swiftly!) with RegexChecker.NOTE. All regex checks are performed in a case-sensitive way by default.
Even thought some storages (e.g. MemoryStorage) allow you to specify regex modifiers within the regex string, we do not translate regex modifiers to all storages (e.g. SQLStorage). Also see warning belowWARNING. Please note, that storages have varying level of regexp support. For example,
most SQL databases allow to use POSIX metacharacters whereas pythonremodule
and thus MemoryStorage does not. So, while defining policies you're safe and sound
as long as you understand how storage of your choice handles the regexps you specified.StringExactChecker - the most quick checker:Checker that uses exact string equality. Case-sensitive.
E.g. 'sun' in 'sunny' - False
     'sun' in 'sun' - TrueStringFuzzyChecker - quick checker with some extent of flexibility:Checker that uses fuzzy substring equality. Case-sensitive.
E.g. 'sun' in 'sunny' - True
     'sun' in 'sun' - TrueNote, that someStoragehandlers can already check if Policy fits Inquiry infind_for_inquiry()method by performing specific to that storage queries - Storage can (and generally should)
decide on the type of actions based on the checker class passed toGuardconstructor
(or tofind_for_inquiry()directly).Regardless of the results returned by a Storage the Checker is always the last row of control
before Vakt makes a decision.Back to topGuardGuard component is a main entry point for Vakt to make a decision. It has one methodis_allowedthat passed anInquirygives you a boolean answer: is that Inquiry allowed or not?Guard is constructed withStorageandChecker.Policies that have String-based type won't match if RulesChecker is used and vise-versa.st=MemoryStorage()# And persist all our Policies so that to start serving our library.forpinpolicies:st.add(p)guard=Guard(st,RulesChecker())ifguard.is_allowed(inquiry):return"You've been logged-in",200else:return"Go away, you violator!",401To gain best performance readCachingsection.Back to topStorageStorage is a component that gives an interface for manipulatingPoliciespersistence in various places.It provides the following methods:add(policy)# Store a Policyget(uid)# Retrieve a Policy by its IDget_all(limit,offset)# Retrieve all stored Policies (with pagination)retrieve_all(batch)# Retrieve all existing stored Policies (without pagination)update(policy)# Store an updated Policydelete(uid)# Delete Policy from storage by its IDfind_for_inquiry(inquiry)# Retrieve Policies that match the given InquiryStorage may have various backend implementations (RDBMS, NoSQL databases, etc.), they also may vary in performance
characteristics, so seeCachingandBenchmarksections.Vakt ships some Storage implementations out of the box. See below:MemoryImplementation that stores Policies in memory. It's not backed by any file or something, so every restart of your
application will swipe out everything that was stored. Useful for testing.fromvaktimportMemoryStoragestorage=MemoryStorage()MongoDBMongoDB is chosen as the most popular and widespread NO-SQL database.frompymongoimportMongoClientfromvakt.storage.mongoimportMongoStorageclient=MongoClient('localhost',27017)storage=MongoStorage(client,'database-name',collection='optional-collection-name')Default collection name is 'vakt_policies'.Actions are the same as for any Storage that conforms interface ofvakt.storage.abc.Storagebase class.Beware that currently MongoStorage supports indexed and filtered-outfind_for_inquiry()only for
StringExact, StringFuzzy and Regex (since MongoDB version 4.2 and onwards) checkers.
When used with the RulesChecker it simply returns all the Policies from the database.SQLSQL storage is backed by SQLAlchemy, thus it should support any RDBMS available for it:
MySQL, Postgres, Oracle, MSSQL, Sqlite, etc.Given that we support various SQL databases via SQLAlchemy, we don't specify any DB-specific drivers in the vakt
dependencies. It's up to the user to provide a desired one. For example:psycopg2orPyMySQL.Example for MySQL.fromsqlalchemyimportcreate_enginefromsqlalchemy.ormimportsessionmaker,scoped_sessionfromvakt.storage.sqlimportSQLStorageengine=create_engine('mysql://root:root@localhost/vakt_db')storage=SQLStorage(scoped_session=scoped_session(sessionmaker(bind=engine)))# Don't forget to run migrations here (especially for the first time)...Beware that currently SQLStorage supports indexed and filtered-outfind_for_inquiry()only for
StringExact, StringFuzzy and Regex checkers.
When used with the RulesChecker it simply returns all the Policies from the database.Note that vakt focuses on testing SQLStorage functionality only for two most popular open-source databases:
MySQL and Postgres. Other databases support may have worse performance characteristics and/or bugs.
Feel free to report any issues.RedisRedis storage.RedisStorate stores all Policies in a hash whose key is the collection name and the hash'es key value pairs are
Policy UID -> serialized Policy representation.Default collection name is "vakt_policies".You can use different Serializers. Any custom or one of the vakt's native.
Just pass it to theRedisStorageconstructor.Vakt is shipped with:JSONSerializerPickleSerializer- the fastest. Used as the default one.Due to serialization/deserialization Redis is not as fast as simpleMemoryStorage.
You can run thebenchmarkand check performance for your use-case.fromredisimportRedisfromvakt.storage.redisimportRedisStorageclient=Redis('127.0.0.1',6379)yieldRedisStorage(client,collection='optional-policies-collection-name')client.flushdb()client.close()...Back to topMigrationvakt.migrationis a set of components that are useful from the perspective of theStorage.
It's recommended to favor it over manual actions on DB schema/data
since it's aware of Vakt requirements to Policies data. But it's not mandatory, anyway.
However it's up to a particular Storage to decide whether it needs migrations or not.
It consists of 3 components:MigrationMigrationSetMigratorMigrationallows you to describe data modifications between versions.
Each storage can have a number ofMigrationclasses to address different releases with the order of the migration
specified inorderproperty.
Should be located inside particular storage module and implementvakt.storage.migration.Migration.
Migration has 2 main methods (as you might guess) and 1 property:up- runs db "schema" upwardsdown- runs db "schema" downwards (rolls back the actions ofup)order- tells the number of the current migration in a rowMigrationSetis a component that represents a collection of Migrations for a Storage.
You should define your own migration-set. It should be located inside particular storage module and implementvakt.storage.migration.MigrationSet. It has 3 methods that lest unimplemented:migrations- should return all initialized Migration objectssave_applied_number- saves a number of a lst applied up migration in the Storage for later referencelast_applied- returns a number of a lst applied up migration from the StorageMigratoris an executor of a migrations. It can execute all migrations up or down, or execute a particular migration
ifnumberargument is provided.Example usage:frompymongoimportMongoClientfromvakt.storage.mongoimportMongoStorage,MongoMigrationSetfromvakt.storage.migrationimportMigratorclient=MongoClient('localhost',27017)storage=MongoStorage(client,'database-name',collection='optional-collection-name')migrator=Migrator(MongoMigrationSet(storage))migrator.up()...migrator.down()...migrator.up(number=2)...migrator.down(number=2)Back to topCachingVakt has several layers of caching, that serve a single purpose: speed up policy enforcement decisions.
In most situations and use-cases you might want to use them all, thus they are designed not to
interact with each other, but rather work in tandem
(nonetheless you are free to use any single layer alone or any combination of them).
That said let's look at all those layers.CachingRegexCheckerIt's relevant only forRegexCheckerand allows to cache parsing and execution of regex-defined Policies,
which can be very expensive
due to inherently slow computational performance of regular expressions and vakt's parsing. When creating aRegexCheckeryou can specify a cache size for an in-memoryLRU (least recently used)cache. Currently
only python's native LRU cache is supported.# preferably size is a power of 2chk=RegexChecker(cache_size=2048)# or simplychk=RegexChecker(2048)# or 512 by defaultchk=RegexChecker()Caching the entire Storage backendSome vakt's Storages may be not very clever at filtering Policies atfind_for_inquiryespecially when dealing with
Rule-based policies. In this case they return the whole set of the existing policies stored in the external storage.
Needless to say that it makes your application very heavy IO-bound and decreases performance for large policy sets
drastically. Seebenchmarkfor more details and exact numbers.In such a case you can useEnfoldCachethat wraps your main storage (e.g. MongoStorage) into another one
(it's meant to be some in-memory Storage). It returns you a Storage that behind the scene routes all the read-calls
(get, get_all, find_for_inquiry, ...) to an in-memory one and all modify-calls (add, update, delete) to your main Storage (
don't worry, in-memory Storage is kept up-to date with the main Storage). In case a requested policy is not found in in-memory Storage
it's considered a cache miss and a request is routed to a main Storage.Also, in order to keep Storages in sync,
when you initializeEnfoldCachethe in-memory Storage will fetch all the existing Policies from a main one -
therefore be forewarned that it might take some amount of time depending on the size of a policy-set.
Optionally you can callpopulatemethod after initialization, but in this casedo not ever call any modify-related methods of
EnfoldCache'd storage beforepopulate(), otherwise Storages will be in an unsynchronized state and it'll
result in brokenGuardfunctionality.fromvaktimportEnfoldCache,MemoryStorage,Policy,Guard,RegexCheckerfromvakt.storage.mongoimportMongoStoragestorage=EnfoldCache(MongoStorage(...),cache=MemoryStorage())storage.add(Policy(1,actions=['get']))...guard=Guard(storage,RegexChecker())Caching the GuardGuard.is_allowedit the the centerpiece of vakt. Therefore it makes ultimate sense to cache it.
Andcreate_cached_guard()function allows you to do exactly that. You need to pass it a Storage, a Checker and a
maximum size of a cache. It will return you a tuple of: Guard, Storage and AllowanceCache instance:You must do all policies operations with the returned storage
(which is a slightly enhanced version of a Storage you provided to the function).The returned Guard is a normal vakt'sGuard, but itsis_allowedis cached withAllowaceCache.The returned cache is an instance ofAllowaceCacheand has a handy methodinfothat provides current state of the cache.How it works?Only the first Inquiry will be passed tois_allowed, all the subsequent answers for similar Inquiries will be taken
from cache.AllowanceCacheis rather coarse-grained and if you call Storage'sadd,updateordeletethe whole
cache will be invalided because the policy-set has changed. However for stable policy-sets it is a good performance boost.By defaultAllowanceCacheuses in-memory LRU cache andmaxsizeparam is it's size. If for some reason it does not satisfy
your needs, you can pass your own implementation of a cache backend that is a subclass ofvakt.cache.AllowanceCacheBackendtocreate_cached_guardas acachekeyword argument.guard,storage,cache=create_cached_guard(MongoStorage(...),RulesChecker(),maxsize=256)p1=Policy(1,actions=[Eq('get')],resources=[Eq('book')],subjects=[Eq('Max')],effect=ALLOW_ACCESS)storage.add(p1)# Given we have some inquiries that tend to repeatinq1=Inquiry(action='get',resource='book',subject='Max')inq2=Inquiry(action='get',resource='book',subject='Jamey')assertguard.is_allowed(inq1)assertguard.is_allowed(inq1)assertguard.is_allowed(inq1)assertnotguard.is_allowed(inq2)assertguard.is_allowed(inq1)assertguard.is_allowed(inq1)# You can check cache stateassert4==cache.info().hitsassert2==cache.info().missesassert2==cache.info().currsizeBack to topJSONAll Policies, Inquiries and Rules can be JSON-serialized and deserialized.For example, for a Policy all you need is just run:fromvakt.policyimportPolicypolicy=Policy('1')json_policy=policy.to_json()print(json_policy)# {"actions": [], "description": null, "effect": "deny", "uid": "1",# "resources": [], "context": {}, "subjects": []}policy=Policy.from_json(json_policy)print(policy)# <vakt.policy.Policy object at 0x1023ca198>The same goes for Rules, Inquiries.
All custom classes derived from them support this functionality as well.
If you do not derive from Vakt's classes, but want this option, you can mix-invakt.util.JsonSerializerclass.fromvakt.utilimportJsonSerializerclassCustomInquiry(JsonSerializer):passBack to topLoggingVakt follows a common logging pattern for libraries:Its corresponding modules log all the events that happen but the log messages by default are handled byNullHandler.
It's up to the outer code/application to provide desired log handlers, filters, levels, etc.For example:importloggingroot=logging.getLogger()root.setLevel(logging.INFO)root.addHandler(logging.StreamHandler())...# here go all the Vakt calls.Vakt logs can be comprehended in 2 basic levels:Error/Exception- informs about exceptions and errors during Vakt work.Info- informs about incoming inquiries, their resolution and policies responsible for this decisions
('vakt.guard' and 'vakt.audit' streams).Back to topAuditVakt allows you to not only watch the incoming inquiries and their resolution, but also keep track of the policies
that were responsible for allowing or rejecting the inquiry. It's done via audit logging.Audit logging is implemented within a standard Python logging framework.
You can enable it by subscribing to an audit ('vakt.audit') logging "stream".Example of configuration in the code:importlogginglogger=logging.getLogger('vakt.audit')logger.setLevel(logging.INFO)fmt='msg:%(message)s| effect:%(effect)s| deciders:%(deciders)s| candidates:%(candidates)s| inquiry:%(inquiry)s'fileHandler=logging.FileHandler('test.log')fileHandler.setFormatter(logging.Formatter(fmt))fileHandler.setLevel(logging.INFO)logger.addHandler(fileHandler)...# here go all the Vakt calls.Vakt logs all audit records at theINFOlevel.The formatter supports the following fields:message - the message that tells what and why happened in the audit.effect - effect that this decision has: 'allow' or 'deny'.candidates - potential policies that were filtered by storage and checkers and may be responsible for the decision.deciders - policies that are responsible for the final decision.inquiry - the inquiry in question.all the standard Python logging fields like time, level, module name, etc.Thedecidersandcandidatesfield can be logged in various ways depending on the theaudit_policies_cls.
It can be passed to theGuardconstructor.Vakt has the following Audit Policies messages classes out of the box:PoliciesNopMsgPoliciesUidMsg (is the default one)PoliciesDescriptionMsgPoliciesCountMsgRefer to their documentation on how they represent the policies.WARNING. Please note, that if you have Guard caching enabled, then audit records for the same subsequent inquiries won't be
logged because the calls are cached. However the log records from 'vakt.guard' stream will be always logged -
they will tell only was the inquiry allowed or not.Back to topMilestonesMost valuable features to be implemented in the order of importance:SQL StorageRules that reference Inquiry data for Rule-based policiesCaching mechanisms (for Storage and Guard)YAML-based language for declarative policy definitionsEnhanced audit loggingRedis StorageBack to topBenchmarkYou can see how much time it takes for a single Inquiry to be processed given we have a number of unique Policies in a
Storage.
ForMemoryStorageit measures the runtime of a decision-making process for all
the existing Policies whenGuard'scode iterates the whole list of Policies to decide if
Inquiry is allowed or not. In case of other Storages the mileage
may vary since they may return a smaller subset of Policies that fit the given Inquiry.
Don't forget that most external Storages add some time penalty to perform I/O operations.
The runtime also depends on a Policy-type used (and thus checker): RulesChecker performs much better than RegexChecker.Example:python3benchmark.py--checkerregex--storagememory-n1000Output is:Populating MemoryStorage with Policies......................START BENCHMARK!Number of unique Policies in DB: 1,000Among them Policies with the same regexp pattern: 0Checker used: RegexCheckerStorage used: MemoryStorageNumber of concurrent threads: 1Decision for Inquiry took (mean: 0.2062 seconds. stdev: 0.0000)Inquiry passed the guard? FalseScript usage:usage: benchmark.py [-h] [-n [POLICIES_NUMBER]] [-s {mongo,memory,sql,redis}] [-d [SQL_DSN]] [-c {regex,rules,exact,fuzzy}]
                    [-t [THREADS]] [--regexp] [--same SAME] [--cache CACHE] [--serializer {json,pickle}]

Run vakt benchmark.

optional arguments:
  -h, --help            show this help message and exit
  -n [POLICIES_NUMBER], --number [POLICIES_NUMBER]
                        number of policies to create in DB (default: 100000)
  -s {mongo,memory,sql,redis}, --storage {mongo,memory,sql,redis}
                        type of storage (default: memory)
  -d [SQL_DSN], --dsn [SQL_DSN]
                        DSN connection string for sql storage (default: sqlite:///:memory:)
  -c {regex,rules,exact,fuzzy}, --checker {regex,rules,exact,fuzzy}
                        type of checker (default: regex)
  -t [THREADS], --threads [THREADS]
                        number of concurrent requests (default: 1)

regex policy related:
  --regexp              should Policies be defined without Regex syntax? (default: True)
  --same SAME           number of similar regexps in Policy
  --cache CACHE         number of LRU-cache for RegexChecker (default: RegexChecker's default cache-size)

Redis Storage related:
  --serializer {json,pickle}
                        type of serializer for policies stored in Redis (default: json)Back to topAcknowledgementsInitial code ideas of Vakt are based onAmazon IAM PoliciesandLadonPolicies SDK as its reference implementation.Back to topDevelopmentTo hack Vakt locally run:$...# activate virtual environment w/ preferred method (optional)$pipinstall-e.[dev,mongo,sql,redis]# to install all dependencies$pytest-m"not integration"# to run non-integration tests with coverage report$pytest--cov=vakttests/# to get coverage report$pylintvakt# to check code quality with PyLintTo run only integration tests (for Storage adapters other thanMemoryStorage):$dockerrun--rm-d-p27017:27017mongo
$# run sql and Redis database here as well...$pytest-mintegration
$pytest-msql_integrationOptionally you can usemaketo perform development tasks.Back to topLicenseThe source code is licensed under Apache License Version 2.0Back to top