Validating a user provided date with sentinel

I’m trying to write a sentinel policy that will read a date provided in structured data. After reading the docs a few times I’m still unclear what the best approach is. I basically need to have the policy fail if the date is within two weeks. Using the sentinel simulator I’m failing to mock or otherwise provide a date as part of the test configuration.

The test policy I’m using is this.

import "time"

time_now = time.now
print("   Time Now :",time_now.rfc3339)

time_loaded = time.load("3030-08-28T00:00:00Z")
print("Time Loaded :",time_loaded.rfc3339)

if time_now.rfc3339 matches time_loaded.rfc3339 {
    matching_times = true
} else {
    matching_times = false
}

main = rule {
  matching_times
}

Setting the time as a global in test file like so…

{
    "global": {
        "time": {
            "now": {
                "day": 28,
                "hour": 0,
                "minute": 0,
                "month": 8,
                "month_name": "August",
                "rfc3339": "3030-08-28T00:00:00Z",
                "second": 0,
                "unix": 33471014400,
                "unix_nano": -3422473747419103232,
                "weekday": 6,
                "weekday_name": "Saturday",
                "year": 3030,
                "zone": 0,
                "zone_string": "+00:00"
            }
        },
        "test": {
            "main": true
        }
    }
}

This test fails and the date is not being passed to time.now

FAIL - time_mock.sentinel
  FAIL - test/time_mock/global_time.json
    expected "main" to be true, got: false

    logs:
         Time Now : 2019-10-29T21:13:08.563419362Z
      Time Loaded : 3030-08-28T00:00:00Z

    trace:
      FALSE - time_mock.sentinel:16:1 - Rule "main"

Here is the test config file for doing a mock date.

{
    "mock": {
        "time": {
            "now": {
                "day": 28,
                "hour": 0,
                "minute": 0,
                "month": 8,
                "month_name": "August",
                "rfc3339": "3030-08-28T00:00:00Z",
                "second": 0,
                "unix": 33471014400,
                "unix_nano": -3422473747419103232,
                "weekday": 6,
                "weekday_name": "Saturday",
                "year": 3030,
                "zone": 0,
                "zone_string": "+00:00"
            }
        }
    },
    "test": {
        "main": true
    }
}

On the test I configured to mock the date it’s failing strangely. The time is correct but the time.load() is failing for some reason.

  ERROR - test/time_mock/mock_time.json
    Error: time_mock.sentinel:7:15: key "load" doesn't support function calls

    logs:
         Time Now : 3030-08-28T00:00:00Z

    trace:

The code is stored in this repo https://github.com/trodemaster/sentinel-sandbox Looking for suggestions on how to create the policy and test it with dates included in the config file.

Thanks,
Blake

Hi Blake,

If my understanding is correct, you have a requirement for a policy to fail prior to a given date after which time all evaluations for the given policy should complete successfully? Let me know if I have missed anything

Sentinel Policy Definition

Having reviewed your requirements I believe that the following should get you the desired outcome:

import "time"

validate_after = rule {
    time.now.after(time.load("2018-11-30T00:00:00Z"))
}

main = rule {
  (validate_after)
}

Testing

Regarding the error that you are getting when providing mock data. I am getting the same behaviour on my end when I define the following:

{
    "mock": {
        "time": {
            "now": {
                "day": 30, 
                "hour": 0, 
                "minute": 0, 
                "month": 11, 
                "month_name": "November", 
                "rfc3339": "2018-11-30T00:00:00Z", 
                "second": 0, 
                "unix": 1543536000, 
                "unix_nano": 1543536000000000000, 
                "weekday": 5, 
                "weekday_name": "Friday", 
                "year": 2018, 
                "zone": 0, 
                "zone_string": "+00:00"
            }  
        }
    },
    "test": {
        "main": true,
        "validate_after": true
    }
}

Leave this with me and I will work on providing a working example.

1 Like

Hi Blake.

After a bit of trial and error and a lot of research, I now have a working example for you.

One of the interesting challenges in your use case was the mocking of the time.load() which can only be achieved by mocking the data using a mock-time.sentinel file which is documented in the following link.

My final configuration is as follows. If you have any questions, please feel free to post them here and I will respond accordingly. Hope this helps.

Folder Tree

➜  demo tree         
.
├── test
│   └── validate
│       ├── failure.json
│       └── success.json
├── testdata
│   └── mock-time.sentinel
└── validate.sentinel

3 directories, 4 files

validate.sentinel

import "time"

if trigger_failure {
  date_time = "2017-11-30T00:00:00Z"
} else {
  date_time = "2018-11-30T00:00:00Z"
}

validate_after = rule {
    time.now.after(time.load(date_time))
}

main = rule {
  (validate_after)
}

mock-time.sentinel

_validate = false

load = func(t) {
  if t is "2018-11-30T00:00:00Z" {
    _validate = true
  }
  return true
}

now = {
    "after": func(t) {
        return _validate
    },
}

success.json

{
    "global": {
        "trigger_failure": false
    },
    "mock": {
        "time": "../../testdata/mock-time.sentinel"
    },
    "test": {
        "main": true,
        "validate_after": true
    }
}

failure.json

{
    "global": {
        "trigger_failure": true
    },
    "mock": {
        "time": "../../testdata/mock-time.sentinel"
    },
    "test": {
        "main": false,
        "validate_after": false
    }
}

Ok so using sentinel code to mock the date seems reasonable. The mock-time.sentinel doesn’t appear to be providing a timespace that the main policy can act on. To clarify the end goal the main policy will receive a date from request.data.data and then load it as a timespace. Then add two weeks and compare the current time (mocked). The idea being it would fail if the provided date is within two weeks.

Hi @trodemaster. Is this an example of request.data.data that you will be evaluating?

{
    "cred_description": "Funky Service from external vendor",
    "Owner": "Blake Garner",
    "Expiration": "2020-08-16 09:52:05",
    "TeamName":   "IS",
    "Contact": "username@adobe.com",
    "AccountID": "adobeitcfunkyservice223",
    "AccountKEY": "a340b6a946bbdw4515r884dy12b8a484b518",
    "AccountKEYbase64": false
  }

Yes,
I have refined it a bit so that the “Expiration” is in the needed format. Here is what I put together for the sentinel test.

"request": {
    "data": {
        "data": {
            "token_description" : "Cool service owned by my team",
            "token_owner" : "person@company.com",
            "token_expiration" : "2019-08-28T00:00:00+00:00",
            "token_company_service_id" : "123467",
            "token_technical_contact" : "team@company.com",
            "token_id" : "funkyservice223",
            "token_secret" : "a340b6a946bbdw4515r884dy12b8a484b518",
            "token_secret_base64" : false,
            "token_mission_critical" : true
        }
    }
}

Thanks again for looking at this!

Okay, so just so I am clear, you want to do something like this?

Pseudo Code Example

current_date = time.now()
expiration_date = time.load(request.data.data.token_expiration)
future_date = expiration_date.add(1209600000000000)

if expiration_date.after(current_date) == expiration_date.before(future_date)  {
     return true
} else {
    return false
}

Note:
time.now() would be mock data returned from mock-time.sentinel.

Yes that’s the approach I’m looking at.

Hi Blake,

How have you been progressing with your policy? Apologies for not getting back to you sooner but I’ve been pretty busy this end. I carried on a bit of work today and came up with the following policy which I believe we agreed upon previously. This was the easy bit!

import "time"

token_expiry = time.load("2019-11-16T00:00:00Z") // Placeholder for Vault request data
cutoff_time = token_expiry.add(1209600000000000)
current_time = time.now

validate_after = rule {
  token_expiry.after(current_time)
}

validate_before = rule {
  token_expiry.before(cutoff_time)
}

main = rule {
  validate_after and validate_before
}

Mocking has been an entirely different challenge and I’m not sure I have a clear way forward due to the way in which the policy uses the time import. I’m not even sure that attempting to mock the time import is even worth the effort at this stage because, in reality, we aren’t testing the import as the Sentinel team have already done so on your behalf.

I wish I had an answer for you right now but I need to take more time with this, unfortunately.

I have this sentinel work back on deck for next week. Not being able to mock the time really only impacts my ability to use the sentinel cli for testing. As a workaround I can edit the tests every time I want to run them for now.

Thanks for this clean policy snippet I’ll add this in and update next week!

You are welcome. You can also do the following with the timespace.add method which I learnt about yesterday which makes things a bit cleaner and clearer as well:

import "time"

token_expiry = time.load("2019-11-16T00:00:00Z") // Placeholder for Vault request data
cutoff_time = token_expiry.add(time.hour * 336)
current_time = time.now

validate_after = rule {
  token_expiry.after(current_time)
}

validate_before = rule {
  token_expiry.before(cutoff_time)
}

main = rule {
  validate_after and validate_before
}

Hi Blake,

I finally have something that “kind” of mimics the time imports although does not do any time-space validation as that would require me to rewrite the import for time in the mock. I already touched on why this is a bad idea last week so I will just leave it at that :slight_smile:

Anyway, I hope this is useful. It has certainly been a challenging thought exercise for me.

validate.sentinel

import "time"

request_data = request.data.data
token_expiry = time.load(request_data.expiration)
cutoff_time = token_expiry.add(1209)
current_time = time.now

validate_after = rule {
  token_expiry.after(current_time)
}

validate_before = rule {
  token_expiry.before(cutoff_time)
}

main = rule {
  validate_after and validate_before
}

mock-time.sentinel

now = 2782

load = func(t) {
    if t is "2019-11-18T00:00:00Z" {
      unix_nano = 1574
    } else if t is "2019-11-01T00:00:00Z" {
      unix_nano = 1572
    }
    return {
        "add": func(t){
            return unix_nano + t
        },
        "before": func(t){
            if now < t {
              return true
            } else {
              return false
            }
        },
        "after": func(t){
            if t > unix_nano {
              return true
            } else {
              return false
            }
        },
    }
}

success.json

{
    "global": { 
        "request": {
            "data": {
                "data": {
                    "cred_description": "Funky Service from external vendor",
                    "owner": "Blake Garner",
                    "expiration": "2019-11-18T00:00:00Z",
                    "teamname": "IS",
                    "contact": "username@adobe.com",
                    "accountid": "adobefunkyservice223",
                    "accountkey": "a340b6a946bbdw4515r884dy12b8a484b518",
                    "accountkeybase64": false
                }
            }
        }
    },
    "mock": {
        "time": "../../testdata/mock-time.sentinel"
    },
    "test": {
        "main": true
    }
}

failure.json

{
    "global": {
        "request": {
            "data": {
                "data": {
                    "cred_description": "Funky Service from external vendor",
                    "owner": "Blake Garner",
                    "expiration": "2019-11-01T00:00:00Z",
                    "teamname": "IS",
                    "contact": "username@adobe.com",
                    "accountid": "adobefunkyservice223",
                    "accountkey": "a340b6a946bbdw4515r884dy12b8a484b518",
                    "accountkeybase64": false
                }
            }
        }
    },
    "mock": {
        "time": "../../testdata/mock-time.sentinel"
    },
    "test": {
        "main": false
    }
}