 Alright, it looks like it's about time I'm going to get started. My name is Joe Conway, and I work for Crunchy Data, just like several other people here. I'm going to do a talk on MLS Postgres. So the first question is, what is MLS? How many people here in the audience know what MLS is? About half, maybe. So the idea of MLS is multi-level security. So within the database we want to have multiple security levels that we can screen or the rows with. And when we talk about security level, we're talking about two different concepts actually. And I'm going to go into this in more detail, but it's basically a sensitivity level and a category. A sensitivity level is kind of like the top secret versus secret versus confidential kind of thing for the military. Whereas category is a way to classify a particular piece of information so that you can only disclose it to people who need to know it regardless of their level. The technologies I'm going to talk about here are Postgres, obviously. Specifically, roll-level security, which is a new feature in 9.5, written mostly, I think, by Stephen Frost in the back of the room, but with help by other people. And we're also going to use a customized version of SCPG SQL. I'll just tell you up front, SCPG SQL customizations that we're doing at the moment are not available publicly, but it is our intent to publish them as open source one way or another over the coming months, let's say. So some of the stuff that we're doing will probably get pushed back into the main SCPG SQL and Postgres itself, and some things may not get accepted for whatever reason because not enough people are interested in the specific feature. If that type of thing happens, then we'll have our own open source repository for it. We'll also be talking about Red Hat Enterprise Linux, specifically the networking SE Linux and some custom SE Linux policy. And again, the custom policy part is not open source yet. I'm going to very briefly show you how we install our own custom policy and we can talk about it a little bit, how that's done. But I'm not going to go into a great deal of detail on that. So in terms of some caveats, this talk is covering putting up a development system, not a production system. So some of the things I'm doing on here you wouldn't want to be doing on a production system, just keep that in mind. I'm also going to demo what I'm basing the talk on is a system that's in permissive mode. Get into that a little bit more, but basically that means SE Linux is in a condition where it's not actually enforcing the rules for the entire system. Although as you'll see that we have set it up so that the rules will be enforced within Postgres for the purposes of testing the system. And this whole thing is a work in process. A lot of other people have been involved in this. It's not something that I've done. I've been involved in it as well for about the last six months. But as I said, Stephen Frost is a major part of this. There's other guys, the crunchy Adam, Mike Palmiato, Jason, that have all been involved in it heavily. We've had input from both Red Hat and Lockheed Martin. And there's undoubtedly other people that I'm not mentioning here. So in terms of the agenda, I'm going to cover the components, RLS, SE Linux and SCPG SQL. And then I'm going to go through an implementation. So literally bash script and code that's necessary to make this whole thing work. And then I'm going to show you what it looks like in the database once this is all set up. Now as I said, there's a lot of slides to this. I have an hour to deliver the talk. I'm going to probably need most of it. So I'll be going through some of these slides fairly quickly. If there's a question that you have on something and I'm not able to get to it during the talk, feel free to find me after the talk. I'll also mention, since other people have brought it up, there is now a wiki page on the PostgresSQL.org wiki that has all the different conferences. And we've added a link for this conference. And then on that link you should find eventually as the authors put them up the slides. The role level security is a new feature in Postgres 9.5. You can enable this on a per table basis. And it's enforced basically with one or more policies on a table. And the policies use either a using expression or a with check expression. And the difference is basically the using expression is looking at the old row and kind of filtering what you can see. And the with check expression is looking at the new row and preventing you from doing something that you're not allowed to do. So the with check if you violated throws an error whereas the using just filters the rows out. So what does this look like? This is not an MLS version of it. This is just a very simple version of RLS just to get you familiar with the concepts. So if I create two users, Bob and Alice, and I create this table, insert some values into it. And in this app user column I'm going to put Bob and one Alice in the other. And now when I alter the table to enable row level security and I create a policy that's saying that this app user column has to equal the current user. And then I'm going to grant select on the table. So now when I log in as super user, I see both of the rows. But when I log in as Bob, I only see Bob's row. When I log in as Alice, I only see Alice's row. Any questions about that? So security enhanced Linux. How many people here are familiar with SC Linux in about half? So SC Linux is a mandatory access control system. It's, that's different from discretionary access control. And that discretionary access, the classic example is that I as a user, logged into a system, create a file. I can modify that file so that it's world readable, world writable, world executable, right? But with a mandatory access control system, the sysadmin has control over who can actually do what with that file. And that's what SC Linux is going to do for you. It's enforced in kernel space. It's managed via, again, a policy. And it's actually many, many policies that are in something called a reference policy. Red Hat distributes Red Hat 7 by default with SC Linux on an enforcing mode and targeted. And the difference between targeted and MLS is that targeted specifically aims at controlling access to just certain services. But the rest of the system is kind of as if SC Linux weren't even there. It tries not to get in your way. MLS is different in that it's not only going to enforce the policies, it's going to enforce them across everything. And it's also going to enforce them with multiple levels. And it can be customized with your own custom policy modules. Here on the slide I provided a link to something that was put together by one of the guys at Red Hat. It's kind of a silly looking coloring book. But it's actually a really good kind of tutorial to get you familiar with SC Linux. So if you want to know kind of quickly and easily what SC Linux is all about, that's kind of a good place to start. So the MLS reference policy is based on something called the Bell Lopagula model. It was developed, I think, in the early 70s. People write papers on this kind of thing. You could go on and on about what that means. But kind of boiling it down in a nutshell, it means that you can read down and you can write up. So if I have top secret clearance, I should be able to read classified or unclassified material. But if I'm logged in top secret, I shouldn't be able to write something below my level because whatever I write might be top secret. Now Red Hat, by default, modifies this so that instead of write up, which would mean that if that would mean that someone who, for instance, could only read classified information would be able to write a file that was top secret. But by default, they've actually modified it so that it's write equals so that you're always basically writing at whatever level you're effectively logged in as. This context is something that's kind of part and parcel with SC Linux. This is how it works. There's a five segment context with a user, a role, a domain, a sensitivity in a category. So the user is actually the SC Linux user as opposed to say the OS user or the database user. Now we're going to have a way to map, and I'll show you that later, between the Postgres user and the SC Linux user that we want them to behave as. But it's distinct from those other types of users. A role in SC Linux, unlike in Postgres, a role in SC Linux is more like a grouping. In Postgres, a role can be a login role, right? So sometimes the terminology can get a little confusing. A domain is a type of a file or an action. I'll show you an example down here. We'll get to that in a minute. Sensitivity is something that goes from low to high. So you have levels, by default, S0 through S15. You can actually rename these things. You could call it unclassified, classified, secret, top secret if you wanted. I've seen it written as system low to system high. Basically you could rename this as anything that makes sense for your environment. And categories is compartmentalization that I was talking about before. So that gets into need to know or specific projects. So if you have a bunch of people who are, you know, able to read at a certain level, but they're not involved in a particular project, you don't want them necessarily seeing that information. So in terms of the level, the security level is the combination of these two things. And there's some examples here on the screen. This is like a typical database user might be. This database user S6, DB Client R, DB Client T, which is the type, and S0 means they're at the lowest level. Here's an example of a context that might be associated with an object. So system U, object R, SEPG SQL table T. So this is, we're labeling this thing as a SEPG SQL table. And we're giving it a range of levels and a range of categories. So both of those things are possible as well. And to go into a little bit more detail, S0 through S15 represents a range of sensitivities. And that's hierarchical. So if you've got level S15, you should be able to read down to S0. Whereas this category, you represent a range of those with a dot instead of a dash. And I actually heard a talk by one of the guys at Red Hat who said that was accidental. And the first ref policy, apparently he meant them to be the same, but it somehow got published that way and they decided to keep it. But in any case, this means that you've got that span of categories, but they're not hierarchical in the sense that a higher number includes lower number. Any questions about that? This means that you are including this entire range of potential categories for that user. Exactly. And if he has C1023, it doesn't mean that he automatically has access to something with C1, right? So in terms of the way SC Linux makes an access decision, it takes a context label for what's called the subject, which in this case is going to be our Postgres user. It takes a context label for the object or the target, which is going to be our table or our row or whatever. There's a permission involved. What is it we're trying to do? And then the first thing it needs to look at is this type enforcement. So the type part of those labels are compared to see if this type has an allow rule on that type for the type of action I want to do. So that's the first check that's going to happen. And that happens in targeted mode as well as in MLS mode. But what MLS has on top of it is that it looks at the sensitivity in the category. They use the term dominate. So the sensitivity or the classification level, the subject must dominate the object. So in this case, if I've got this example where S5, C1 to C5, it dominates S3 because it's higher in terms of classification level. But in terms of the category, it actually doesn't include the same category. So you would not get access based on this example. So SCPG SQL is a Postgres extension that gets chipped with Postgres. Postgres itself supports a command called security label. And basically, it is just a place to store a label. But Postgres doesn't really manage anything to do with that label. It depends on what's called a label provider. And SCPG SQL is one of these label providers. It's possible that other label providers could be used. I'm not exactly aware of any others that even exist. Stephen, do you know of any other label providers besides SCPG SQL? Yeah, the security label here. So we've customized this with additional functionality, which is basically, we've done this user mapping of the database user to an SE Linux user. And we actually tried to use the security label on a roll to store that information and have SCPG SQL access it. But because of limitation, the way Postgres bootstraps on authentication, we actually couldn't do that in 9.5. So we ended up creating an external file for that mapping. In 9.6, though, there has been a patch that was accepted and committed that will fix that problem for us. The subject for the person logging in is going to get a context transition, which means basically their context is going to be calculated based on their Postgres user, which is mapped to an SE Linux user, as well as the label on the network connection that they're connecting over. We'll see a little bit more about that later. And then we've created these functions, check row label and create row label, which we're going to use in conjunction with this RLS in order to do the row filter. So in terms of what SCPG SQL itself supports by standard, it supports labeling of schemas, tables, views, columns. We've added this custom support for rows. So what does that look like? SCPG SQL check row label. The object context is basically passed in as the first argument. So that's going to be the row security label. The subject context is derived, as I said before, from the SE Linux user that the Postgres user's mapped to plus the labeling on the network that they came in over. And then the permission type is going to, if you don't specify a second argument, it's going to default to select, but you can specify a second argument and basically say I want to do a delete, for instance. And that's an example I'll show you later. And then the access decision, whether or not you get the access is made by SE Linux. So what does this look like? When I do this SCPG SQL get con, this is getting the context of the current user. So my current user logged in is at level 5, S5C1. And if I select that function using this context as if it were a label coming from a table, here I've got something that's labeled as S0 and I'm trying to select it so that comes back as true. So I'm going to get, this function would actually give me access to that row. But this label here with S6, which is above my level, is going to come back as a fault. Any question about that? So here's another example using the second argument and doing deletes. In this case, what you'll see here is if I'm logged in again as S5C1 and I'm going to try and delete a row that's, even though it's below my level, that's going to be not allowed. And if I try and delete a row that is at my level but doesn't have my same category label, it's also going to get denied. But when I try and delete a row that's exactly the same as what I'm logged in as, I'm able to delete it. This SCPG SQL createRowLabel is what we're using to actually label the rows as a new row goes in. This function I created here is just to facilitate writing this query a little bit more compactly just to show me the label on a particular table. So in this query, you see that I've got Table T1 and its label looks like this. System U object are SCPG SQL Table T. And you notice that it's got a full range of basically levels and categories associated with it. And that's so that we can put rows of any level or category into this table. The S contact is the subject. So that's my logged in user. In this case, I'm logged in as CVS5, which happens to have an S5 C1 level. And then the security label on the row that would get created if I were to insert a row is going to look like this. It ends up being a derivation of both of these two things. I get the users, the SE Linux user from the logged in context. My roll and type are inherited from the table. And then my security level is going to actually also come from my logged in user. Now, this is something that in your own environment, you might need to tune and have this work differently. This is the way we needed it to work for the first project that we're working on with this. But there are conceivably other ways you would want this to work. And so you would have to make the changes to make that happen. And this is just another example of a security label that gets created if with a logged in user that is at a higher level. So this one is S6 instead of S5. I'm going to show you that we have to create a column in the table. And that's a consequence of the way row-level security works, which is a result of a long, I think, discussion that happened on the community list, on the hackers list about how row-level security should work. If you built that in somehow, it would be much less flexible. I think was the bottom line to all that. Do you have any comment on that? Okay, so in terms of the implementation, I'm going to take you through a bunch of screens that show how I set all of this up. First thing, download and install Red Hat for CentOS 7.2. This whole talk, if you wanted to try and replicate it, is based on doing a GNOME desktop configuration when I was doing the installation. So in terms of the packages that I got right out of the box, it was based on making that selection. And then I'm going to install some additional packages. The extra packages that are needed for this, at a bare minimum, you don't necessarily need this Apple release, but it does enable you to get some things that aren't available by default that are useful if you're building your own stuff. So although I don't go into it, the talk was already too long and I was going to go into more about building from source in this environment, but there really wasn't enough time for it. But Ccache is something that's very useful if you're doing repeated soft source code builds, and that's only available if you've got Apple installed. So we're going to install all the Postgres Development Group 9.5 RPMs, and then we're going to install a bunch of RPMs for SE Linux. In terms of the networking, you need to configure the interfaces for this. In this case, I'm going to have an admin subnet, and then I'm going to have a subnet per security level that I want to have. So I'm basically modeling this off of what you would typically see in the military where you've got unclassified, classified secret and top secret. But again, these could be anything you need them to be. You have to set up routes. We'll go through how to set up NetLabel, SSH, and Firewall. So the recent versions of Network Manager actually have this useful feature that you can create multiple IP addresses all on one interface. So for setting this up, I had a system with one network card, not five, and it was convenient to just set it up with separate subnets all on the same interface. This is the way you would do that. It's fairly straightforward. The trick is that doesn't automatically install the routes for you. And if you don't have the routes set up, you won't be able to get to these networks. So you need to set up routes. Now we're going to get into NetLabel. NetLabel is a module that allows us to say that anything that connects on a particular subnet is going to get a context label that we were just talking about before based on the subnet. So I'm not going to try and explain in detail every one of these commands, but basically I'm adding a default address for each of my subnets. I've got 4.25.20 down to 8.20. On my admin subnet, this is the command that's going to add this label. So systemUObjectRNetLabel peerT, S0 through S15. So the full range, you're high to low, because this is my admin interface. Whereas my lowest level interface, 5.0, is going to just get an S0. And then similarly, 6.7.8 are going to be labeled as 4, 5, and 6 for the purposes of this talk. So you can think of 0 as being unclassified, 4 as being classified, 5 as being secret, and 6 as being top secret. And then I also did a label for a catch-all for anything that comes in, for instance, routed through my firewall from external to facilitate having someone else help me debug all this. So once that file is configured, you use system control to enable the service. So I'm going to enable NetLabelService. I'm going to start it up. One little bit of advice here is if you do edit that com file for NetLabel, you need to run this NetLabelConfigReset afterwards, otherwise those settings won't take effect. This is one of these pro tips that if you don't know about, it can trip you for a while. So after you run that, you can start and stop the NetLabel service, and then it will pick up the changes that you made. Now SSH, we need to change SSH. By default, SSH runs the service, and I'm not sure I know the exact technical detail for why we need to switch it to running off of sockets, but I believe basically the service shares one socket amongst all the connections. And when you run it as a SSH socket, what happens is each connecting client gets its own socket, which therefore can be labeled by NetLabel, and that's how the NetLabeling happens. Basically, in that mode, what happens is you're using X and NetD to spin off SSH every time someone connects. So it's not necessarily as efficient as running it as a service, but in this environment, it's probably not your top concern. So in order to enable this, I'm going to go into this LibSystemDSystemSSHD socket. I need to add this SE Linux context from net equals true into that file before I start up this service. And then the comment I made here is that I found out the hard way that if you do this and start up SSHD in this mode and NetLabel is not configured correctly, you can no longer SSH in. So I had to actually go rescue the box that I was working on remotely and bring it back to my house because I locked myself out the first time I went through this process for putting together these slides. So the next thing we have to do is swap the enabled service. So I'm going to disable the SSHD service, and I'm going to enable the SSHD socket, and then I'm actually going to stop the old one to start the new one. Finally, if you want to be able to connect to this box over these subnets that you set up, you've got to have firewall rule in place that allows it. So this is how you do it on Red Hat 7. Depending on the system you're on, it might be very different. So I guess a lot of this presentation, this presentation is Red Hat 7 specific and that is using all the system D stuff and the firewall command, make it permanent, add service postgres, is really all you've got to do and then reload the firewall and suddenly you can connect remotely. Any questions on all that? So now SE Linux. As I said earlier, Red Hat 7 defaults to the targeted reference policy and enforcing mode. And certainly at the beginning, if you're going to turn on MLS, you're going to want to be in permissive mode until you get some of the kinks worked out. So we're going to edit the ETC SE Linux config file, set SE Linux to permissive, and set SE Linux type to MLS. And I'm furthermore going to use this set and force command zero, which basically says put SE Linux in permissive mode for the rest of this. When you do switch from targeted to MLS and for a variety of other reasons, you end up having to relabel the file system. What that means is when we were talking about those contexts on the object that have a specific user role, type, or domain, well when we were in targeted we didn't have levels. Now we're going to have levels. So everything, every file on the file system has to be relabeled with a new context based on the policy that you're now using. So this auto in your, right in your root directory, if you create a file called .autorelabel, basically the next time you reboot, every file on the file system is going to get relabeled. This dash F into the autorelabel. This was another one. It took me a while to figure out why we were doing this because I kind of inherited this from the other guys in the group who had inherited it from somebody else, and actually we didn't understand why that was necessary and with a little digging through the source code, figured out that what that does is when the relabeling happens, the underlying commands that do the relabeling, if you don't have them in dash F, which is force, if there's a context already on a file, it doesn't try and change the security levels. Or if you want to force everything to be completely relabeled based on the policy, you need that force. The other thing I've done here is when I first set up the system, as I said, I installed it with a GUI, and at this point I disabled the GUI by setting the default to multi-user target, which is basically just a machine with no GUI. So after I reboot, I can use this sestatus command to see what things look like. Now, as I mentioned, we have a custom policy module that we developed and will eventually get open sourced. I'm not going to go into a lot of detail about it right now, but we've got it set up so basically you just do make, make, install, and our custom policy will get installed. After doing that, and that's basically what we're doing is we're creating these custom roles for Postgres. So we now got a guest role, a DV staff, a DV admin. I think DV admin R actually exists in the reference policy, but the rest of these are roles that we're adding. So now I need to create users, SC Linux users, that I'm going to later map to my database users. So this is the command SCManage user. I'm going to add, I'm specifying that these roles should be associated with this user, and that this range of levels should be associated with this user. In this case, this is Postgres underscore U. I've got a database owner. I've got a staff, guest. And then actually in my examples later on, I use these dbsu0, dbsus4, dbs5, dbs6. And those are going to correlate with the levels that I had on my network. I also need to create some default context files so that when I log in, basically this does a mapping of if I log in and I've got this context, it's going to map me to this context. So it's one of these for each of those roles I just created. Finally, I'm going to initialize Postgres. So with the default Postgres development group packages, there's now a Postgres equals 9.5 setup command that you can use for init to be. Then I'm going to enable the service with system control, start it up, and then just basically check to make sure it's running. So I can use system control status, and I can also just try and list the databases and make sure everything is actually running the way I expect. So now I'm going to configure access to Postgres with the pghbacomp file. And here what I wanted to do is I just wanted to comment out all the existing lines and then add my own. So here I've got local connections, local host connections, and then on all my subnets, I could have done separate lines for each of my subnets, but I was kind of lazy here and just did slash 16. So basically they're all going to require an mv5 login. I created a password for the Postgres user, and then I'm going to reload Postgres using the system control in order to make sure that this takes effect. So now I'm just going to connect and create some roles to make sure I can do it. This command is just going to pipe these create users into psql using the Postgres database user. Is everyone familiar with this GearDoc style? This is a Linux conference. Okay, so at this point I'm going to build and install the custom SCPG SQL module. We've renamed it for our own purposes the crunchy SC Linux, but it's basically SCPG SQL with our additional changes to it. So if you change the directory where you've got the source, this is the way you would typically build the source code of your own that's an extension of Postgres on a system that's been installed, let's say from RPM. You need to have the development RPM installed in order for this to work. So if you say use pgxs equals one and make, and make install. Now I'm also going to have to tweak postgresql.com. There's some general settings. So first of all I need to tell Postgres to listen to the subnets that I created. I'm going to turn row security on. And I'm going to add my custom module to shared preload libraries. This is one that has to load up on Postgres start up. And then I've got some SCPG SQL specific settings that I'm going to set. So I'm going to enable user transition, which is what inside the customizations allows us to say, this user is going to get a specific context based on their SC Linux context and based on their network label. I'm also going to set a default SC Linux user as a catchall. And then this force RLS is what's going to enable me to actually see the effect of SC Linux decisions even though I'm in permissive mode. So installing SCPG SQL, this is exactly the same way you would install SCPG SQL if it were unmodified. It's a lot different than other extensions. I think because of where it hooks in so early, I've not really tried to chase down exactly why it has to be done this way in single user mode. But basically you have to do shutdown Postgres and then loop through all the databases and install the SQL file. It comes with the extension, kind of the old fashioned way that used to do six or seven releases ago with Postgres when you're installing stuff. And then once that's done, I'm going to start Postgres back up and check the status. And then one last bit of custom configuration is, remember I told you that in 9.5 we were unable to use the security label on the roll, which is where we really wanted to stuff the mapping between SC Linux user and Postgres user. Because of the way Postgres starts up when you authenticate, that was not possible. The patch has gone in and then we'll be in part of 9.6, so we'll be able to fix that later on. But for now we've created this external file that we've just put in the data directory called scpgsqlusers.com. And this is where I'm doing the mapping that says the Postgres user is the Postgres underscore U SC Linux user. And user one is dbs0, user two is dbs4, user three is s5, and user four is s6. So now I'm going to create a database and I'm going to load it with the demo code and I'm going to show you in the next few slides what the important parts of that demo code are. You can see I did that on my admin network, logged in as Postgres. So I'm going to create these four demo users, one through four, and you saw that I just had these maps. So this one's going to be mapped basically to a security level zero, four, five, and six. Now finally we get down to the part where we're going to use all of this stuff together. I'm going to create this table T1 with columns a and b, but it also has a security label column. And that security label column is going to default to this createRowLabel function run over the table, table's OID actually. We're going to grant permissions on that table to all the users. We're going to enable a row level security. And then we're going to create a series of policies. So we're going to create a select policy that says checkRowLabel using the security label column. And I don't need the second argument because I'm just doing a select. We're going to create an insert, and the insert policy is going to use a with check, and it's going to use the createRowLabel. So here we're going to make sure that basically that the row label that would be created matches what it should be. For update we're going to say using checkRowLabel, so in other words I should be able to update rows that I can see, but I also have to make sure that the rows that I can see are at the right level. That the rows that I'm changing are at the right level. And then for delete, I'm going to also have the checkRowLabel, and I'm going to say I need delete permission based on the row label. All based on a C1. The sample data that we start with to see the table, we've got these four rows. One of them is at S0 level. We've got an S4, C1, and S5, C1, and S6, C1. Any questions about all that before I get into the actual showing you how it works? I know it's a lot of information. So the first thing we see is that if we try and log in on an S4, remember this 6.20 network is my S4 subnet. And if I'm trying to log in as the S0 user, user1, on a subnet that's at a higher level than I am, I'm going to get an error. Now this admittedly is not the best error message. We probably need to do something about fixing that. But the bottom line is because I don't have the privilege of, I don't have, as the S0 user, I can only see on classified stuff. I shouldn't be logging in on a classified network. If I log in as the S0 user on the S0 subnet, now you can see I actually can run a command. So I'm going to run this getcon command just to see what the label came out as. So as I log in as the S0 on the S0 subnet, you can see that I'm, my SELinux user is dbs0, just like I would expect. dbclientr, dbclientt, and S0 for a level. Now interestingly, if I log in as the S6 user, this is the user who has top secret, but I'm on the S0 subnet, my user is still going to get marked as S6 to match the user that I logged in as but I'm only at S0 because I'm logged in over an S0 network. So now if I'm an S0 user on an S0 subnet and I select from that tail, remember there were four rows in there, I'm only going to see the one row that's at the S0 level. And if I'm logged in as the S6 user on the S0 subnet, probably not surprising to you now, I still only see one row because I can only see the S0 row when I'm logged in on the unclassed network. Now if I log in as the S6 user on the S6 subnet and I query from that table, I'm going to see all four rows because now I'm on the subnet that is classified for S6 with the user that's classified for S6 and I can see all the rows. So now what happens if I try and do an insert? As an S0 user on the S0 subnet, I'm going to do an insert and you can see the security label that I got was S0, probably expected. Again, if I'm doing this as the S6 user on the S0 subnet and I insert a row, you notice the row still gets that S0 label. Again, because I'm logged in over this unclassed network, the only thing I can create is going to be at that level. And if I log into the S6 network and do the insert, I get an S6 label. If I do an update, as the S0 user on the S0 subnet on an S0 row, everything works as expected, as the S6 user on the S0 subnet S0 row, I can also do an update. And again, the S6 user on the S6 subnet S6 row, I'm able to update that. But if I try to update as the S6 user on the S6 subnet, I can see that S0 row, but if I try and update it, I'm going to get a violation because that row is not at my level. I can, however, change the actual label to match my security level. In which case I can update that row now. Now finally, I delete if I'm doing a as an S6 user on an S6 subnet and I delete the rows. In this case, I'm trying to delete all the rows that are greater than 9. 4 seed rows were all less than 10. All the rows I've inserted, which were 3, were all 10 and above. But you notice I only delete 2 rows. So I was only able to delete the 2 rows that were at the S6 level because I'm logged in at S6. And this query shows you that that 11 is still actually there. I can query it and see that row. So if I want to delete that particular row, I need to log into that S0 network and then I can delete it. And that's it. Any questions? We've discussed that internally and that's I think a matter of round to it to some extent. We have people that are working on some script work that will help simplify some of this. And as we repeat this process over and over again, we'll automate more of it as we go. Like I said, this is a working process. There's still a lot of work that should be done with it. And that's definitely something that would help.