There has been some changes. #Accenture #DatabaseSecurity #InfoSec

Life is busy and good; new Some of y’all may already know, after almost thirty years of working for myself, I accepted a position with Accenture Enkitec Group as Technology Innovation Principal Director and the Global Lead for Database Security. There are a lot of reasons I accepted this position. Primarily, I’ve known the folks from Enkitec for a number of years; they have always impressed me with their knowledge, willingness to share that knowledge, and they get s&*t done. Being part of a global team that is focused to delivering the best for the customer. The opportunity to learn new things; now that’s a big plus for me.

Now, being an employee it’s really new to me, they have these things called HR, People Leads, and policies. Who knew? My People Lead was always my bathroom mirror where I would have long discussions with myself about my future and where I needed to put my focus. Now, I have someone else who I can discuss direction, where my focus should be, and also promote me within and outside of Accenture. This is great! Yea’ even I need to get pointed in the right direction every now and then.

New talks are coming up in Poland, Croatia, North Carolina, Spain, East Coast Oracle Users Group (ECOUG). I’m waiting for acceptance from Croatia, ECOUG, and need to get my abstracts in for Spain. Stay tuned for more details on what talks I have coming up.

Oracle #JSON check constraint #quicktip

I came across an interesting problem, we are using JSON in one of our applications and have no control over the people sending us the JSON data. This has caused some issues with data quality. One thing we needed to do is to check that all the required fields have a value when the data is loaded. I found solving the problem to be quite simple by adding check constraints on the JSON column.

alter table payload add constraint 
payload_first_name_check check
(json_exists(json_document, ‘$.first_name));

alter table payload add constraint
payload_last_name_check check
(json_exists(json_document, ‘$.last_name’));

Now when the documents load, if the first or last name are null, the check constraint is violated and ORA-02290  will be raised.

 Failed to execute: soda insert payload /home/oracle/data/04.json

 java.sql.SQLIntegrityConstraintViolationException: ORA-02290: check constraint (RLOCKARD.SYS_C0012523) violated

#Oracle Database Application #Security book is finally out. #infosec #encryption #audit #SecureCoding #PrivilegeAnalysis #OID #OAM #OIM

https://www.amazon.com/Oracle-Database-Application-Security-Directory/dp/1484253663/ref=sr_1_1?keywords=oracle+database+lockard&qid=1573050833&sr=8-1

It’s been a year long process now the book is finally been released. There are a few things I would have written different and a few other subjects I would have liked to cover. Perhaps that will come in my next book or future posts.

In this book we cover Secure Coding, setting up Encryption, and audit. We also dive deep into performing privilege analysis.

A common #infosec error in @Oracle applications #DBA granted to application account

I’ve been doing this a long time, and there two infosec errors that I keep seeing. Granting DBA to an application and people using the application account. The problem of granting DBA to an application account is compounded when people actually logon to the application account to work.

Oracle has the DBMS_PRIVILEGE_CAPTURE package that is now licensed to Enterprise Edition. It’s a powerful tool to fix over privileged accounts; yet when someone logs on as the application to do dba work, then all bets are off.

1) Don’t grant DBA to application accounts. Figure out what privileges the account needs and grant those privileges.

2) Don’t use an application account to do your work.

3) Use the DBMS_PRIVILEGE_CAPTURE package to analyze what privileges your users are using and dial back over privileged accounts.

Critical #Oracle Database flaw needs to be patched today. #infosec #exploit #java

Critical Oracle Database flaw needs to be patched. The patch is in the July 2018 CPU patch.

The exploit is in the Oracle Java VM. Read:  https://nvd.nist.gov/vuln/detail/CVE-2018-3110

This is an easily exploited flaw, that allows a user with low level privileges ( connect with network access via Oracle Net) can completely hijack the Oracle database. 
Affected versions 11.2.0.4, 12.1.0.2, 12.2.0.1, 18

Patch Information: http://www.oracle.com/technetwork/security-advisory/cpujul2018-4258247.html

Oracle Privilege analysis #Quicktip

Here is a quick tip on Oracle privilege analysis. Frequently I want to find out all of the ways a user can get to an object for any privilege. DBA_TAB_PRIVS and DBA_ROLE_PRIVS are the two views I go to. I want to also see all the privileges that are granted on any object. This is good for starting at the user tracking privileges to the object, it’s also good for starting at an object and walking back to the user.
This query does a pivot on the users and roles to get the path to the object and what privileges are associated with that path.
===========================================================================

SELECT OWNER,
TYPE,
TABLE_NAME,
GRANTEE_FROM,
GRANTEE_TO,
"'SELECT'" SEL,
"'UPDATE'" UPD,
"'INSERT'" INS,
"'DELETE'" DEL,
"'EXECUTE'" EXE,
"'FLASHBACK'" FLSH,
"'ON COMMIT REFRESH'" OCR,
"'ALTER'" ALTR,
"'DEQUEUE'" DEQ,
"'INHERIT PRIVILEGES'" IPRV,
"'DEBUG'" DBG,
"'QUERY REWRITE'" QR,
"'USE'" US,
"'READ'" RD,
"'WRITE'" WT,
"'INDEX'" IDX,
"'REFERENCES'" REF
FROM (SELECT R.GRANTEE "GRANTEE_TO",
T.GRANTEE GRANTEE_FROM,
T.GRANTABLE,
T.owner,
T.table_name,
T.TYPE,
T.PRIVILEGE
FROM DBA_TAB_PRIVS T,
DBA_ROLE_PRIVS R
WHERE T.GRANTEE = R.GRANTED_ROLE (+)
AND t.grantee != 'SYS'
AND t.grantee != 'SYSTEM'
AND R.GRANTEE != 'SYS'
AND R.GRANTEE != 'SYSTEM' )
PIVOT (COUNT(PRIVILEGE) FOR PRIVILEGE IN ('SELECT',
'UPDATE',
'INSERT',
'DELETE',
'EXECUTE',
'FLASHBACK',
'ON COMMIT REFRESH',
'ALTER',
'DEQUEUE',
'INHERIT PRIVILEGES',
'DEBUG',
'QUERY REWRITE',
'USE',
'READ',
'WRITE',
'INDEX',
'REFERENCES'))
ORDER BY TABLE_NAME;

Putting #CodeBasedAccessControl to work. #CBAC #Database #infosec #Oracle #TrustedPath

Grab a cup of coffee or a cup of tea. This is not a short post; There is a lot to explain, and many point are repeated. You need to understand all the in’s and out’s of CBAC. However; once you have an understanding, CBAC really quite easy to impalement.

A lot about Code Based Access Control is not intuitive. In fact a lot of people I talk to are confused about what CBAC is and what CBAC is not. The basic premise of CBAC is that a subprogram of a package can only execute only using the privileges that have been granted to the package through a role.

NOTE: What I’m explaining is a multi-schema model. I’m intentionally keeping the model simple to show just the CBAC aspect of a secure architecture. This model is using three schemas, hr that holds data and hr_api that is going to hold packages that will access the data, and hr_decls that holds common types that are used across schemas. Also note, this is not the full secure architecture. There are a number of elements of my secure architecture I am leaving out to focus on CBAC.

Before we go too far, we need to understand some subtleties about roles. In order to grant a role to a package, the role must first be granted to the owner of the package with either admin option, delegate option, or you must connect as sys to grant the role to the package and the owner of the package must have created the role. Yea’ that’s a bit to digest, so let’s examine the three different types of role grants that must be made.

Three different ways to grant a role to a package.

#1 --As a user with create role privileges and the ability to grant 
   -- the required privileges to the role. ie: DBA
conn rob_dba@demo
create role <role>;
grant <privilege> on <object owner>.<object name> to <role>;
grant <role> to <package owner> with admin option;
conn <package owner>@<instance>
grant <role> to package <package name>;

#1  GRANTING THE ROLE TO THE PACKAGE OWNER WITH ADMIN OPTION: Granting the package owner the role with admin option works, but the package owner can grant the role to other schemas. Using a user with create role privilege and the ability to grant the required privileges on the underlying objects (I normally use my dba account rob_dba). This is an unnecessary security risk. Like I said, the package owner can now grant the role to other schemas, thereby making the trusted path weaker. I’m sure there are use cases for granting the package owner the role with admin option; however that is a corner case and should be justified, not the norm and quite frankly, I can not think of a use case that would justify using this method.

#2 -- as package owner, create the role.the package owner must have 
   -- create role privileges.
conn <package owner>@<instance>
create role <role>;
conn sys@<instance> as sysdba
grant <role> to package <package owner>.<package name>;

#2 USING SYS TO GRANT THE ROLE TO A PACKAGE: The package owner must have the create role privilege and the package owner must have created the role. In this method, we are creating the role as the package owner and connecting as sys to grant the role to the package. I hope there is no need to explain what a bad idea it is to use the sys account. There is a huge security issue, doing work as sys.

#3 --As a user with create role privileges and the ability to grant 
   -- the required privileges to the role. ie: DBA
conn rob_dba@demo
create role <role>;
grant <privilege> on <object owner>.<object name> to <role>;
grant <role> to <package owner> with delegate option;
conn <package owner>@<instance>
grant <role> to package <package name>;

#3 GRANTING THE ROLE TO THE PACKAGE OWNER WITH DELEGATE OPTION: This is the preferred method to grant a role to a package. It appears the same as #1; however we are granting the package owner the role with delegate option. Using this method, we can grant the package the required role, but the package owner can not grant the role outside of it’s schema. This is the least amount of privileges needed to execute the task.

Remember, we want to operate with the least amount of privilege and still be able to do our job.

The hr_decls schema:

My hr_decls schema is used to define common types that will be referenced between schemas. This is not required for using CBAC, however it makes referencing common types between schemas much easier and makes maintenance simpler. Once this package is created, execute is granted to a role (exec_emp_decl_role) and that role is granted to any users that need to use it and not compile code against it. Mainly this grant is for testing purposes. Note: PL/SQL grant execute through a role or grant execute directly? If you are executing a pl/sql package using an anonymous block, you can pick up the privilege through a role. If you are compiling a pl/sql package that references a pl/sql package in a different schema, you must grant execute on the package directly.

conn rob_dba@demo
create role exec_emp_decl_role;
conn hr_api@demo
-- create the decls package. This package is used to create types 
-- that are used between schemas.
create or replace package hr_decls.emp_decl
authid current_user as
  cursor emp_cur is
  select *
  from hr.employees;

  subtype st_emp is emp_cur%rowtype;
  type tt_emp is table of st_emp index by pls_integer;
end emp_decl;
/
-- usr1 (our test user) required this role to reference the package
-- through an anonymous block 
grant execute on exec_emp_decl_role to usr1;
-- hr_api requires a direct grant on the package hr_decls.emp_decl
grant execute on hr_decls.emp_decl to hr_api;

Your privilege on/off switch starts with authid current_user.

create package hr_api.emp_select_pkg
authid current_user as

Hey, we are granting a role to the package, why do we need to grant privileges on the underlying objects directly to the package owner? In order to compile your package, you need to grant privileges on the underlying object to the executing schema.

If you use authid definer (that is the default authid. So if you don’t specify current_user or definer, the package is created with definer’s rights.), the package will pick up the privileges from the schema it resides in. If we grant a lot of privileges to the schema the code resides in, or heaven forbid, the code resides in the schema with the data, and there is a flaw in your code; the hacker owns your database.

By using authid current_user, the package inherits the privileges of the user executing the code. If the user does not have any privileges on the underlying data objects then subprogram in a package can only execute with the privileges granted to the package through a role.

Now let’s start setting this up from the beginning. Connecting as a dba, or another appropriate user that has the proper privileges, we need to create a role, grant privileges to that role, and grant that role to hr_api with admin option.

We are going to create the role hr_emp_sel_role. Then we are going to grant to the role select on hr.employees.

create role hr_emp_sel_role;
grant select on hr.employees to hr_emp_sel_role;

Once we have these roles and grants let’s grant it to hr_api with delegate option.

grant hr_emp_sel_role to hr_api with delegate option;

Here is a  niggle; you need to get your package to compile. The schema, in this case hr_api needs to be granted select on the underlying data. So, we are going to grant select on hr.employees to hr_api. I don’t really like granting privileges directly to a schema, but it’s needed for the code to compile. Using this grant, the code that selects against hr.employees can compile. But remember, the code we are writing will be authid current_user. We now have the underlying roles with the appropriate grants to start building our application.

grant select on hr.employees to hr_api;

There is one more role you need. This is the role that will be granted execute on the api package, that will inturn be granted to the user.

create role EXEC_EMP_SEL_API_ROLE;

Here is the rolls and all the grants we have done so far.

conn rob_dba@demo
create role hr_emp_sel_role;
grant select on hr.employees to hr_emp_sel_role;
grant hr_emp_sel_role to hr_api with delegate option;
grant select on hr.employees to hr_api;
create role EXEC_EMP_SEL_API_ROLE;
grant execute on hr_decls.emp_decl to hr_api;
grant EXEC_EMP_SEL_API_ROLE to usr1;
grant execute on hr_decls.emp_decl to exec_emp_decl_role;
grant exec_emp_decl_role to usr1;

Now we need to connect as hr_api and start building the application. In my environment, hr_api is granted create procedure through a password protected role. We are creating one function in the package, sel_hr_emp_phone that returns an employees information based on the phone number.

conn hr_api@demo
SET role hr_api_admin_role identified by My#Supper7Secret#Password2;
-- note authid current_user.
create or replace package hr_api.sel_emp_phone_api 
authid current_user as
  function sel_hr_emp_phone(p_phone hr.employees.phone_number%type)
  return hr_decls.emp_decl.tt_emp;
end sel_emp_phone_api;
/

We have created the package, now it’s time to grant the hr_emp_sel_role to the package.

grant hr_emp_sel_role to package 
sel_emp_phone_api;

We’ve flipped on the privilege the package needs. So sel_emp_phone_api can only execute with the privileges granted to it from the role, or privileges inherited from the user executing the package. (you must understand, the package can still inherit more privileges from the user.) Let’s finish off by filling in the package details.

create or replace package body hr_api.sel_emp_phone_api 
as

  function sel_hr_emp_phone(p_phone hr.employees.phone_number%type)
  return hr_decls.emp_decl.tt_emp is
  ltt_emp hr_decls.emp_decl.tt_emp;
  begin
    select *
    bulk collect into ltt_emp
    from hr.employees
    where phone_number = p_phone;
    return ltt_emp;
  exception when no_data_found then
    -- <fixme> insert error handler.
    raise_application_error(-20000,'');
  End sel_hr_emp_phone;
end sel_emp_phone_api;
/

We’ve got all the objects we need together, now we need two more grants to run this. We need to grant execute on hr_api.sel_emp_phone_api package to the exec_emp_sel_api_role and we need to grant the role to a user to execute the api. I have a test user usr1 that is used to test various security configurations. Here is the the setup that has already been done.This test user is granted create session, exec_emp_sel_api_role and exec_emp_decl_role.

conn rob_dba@demo
create user usr1 identified by AHotRedKotenok;
grant create session to usr1;
create role exec_emp_decl_role;
grant execute on hr_api.sel_emp_phone_api to exec_emp_sel_api_role;
-- so the user can execute the api through an anonymous block.
grant exec_emp_sel_api_role to usr1;
-- so the user can reference the hr_decls.emp_decl package from an 
-- anonymous block.
grant execute on hr_decls.emp_decl to exec_emp_decl_role;
grant exec_emp_decl_role to usr1;

Now we can test this out. Connect as usr1, and call the function. Let’s see if we get anything back.  

conn usr1@demo
declare
ltt_emp hr_decls.emp_decl.tt_emp;
begin
  ltt_emp := hr_api.sel_emp_phone_api.sel_hr_emp_phone(
              p_phone => '+1.800.555.1212');
end;
/

 

PGA Memory Operation Events

I’ve been working on putting together some performance test for my secure coding talk coming up at Hotsos and encountered something I can not quite explain. This test case does a bulk select into a type and returns the type to the calling program. When I execute the code through the API, I get 1,100+- PGA Memory Operation Events. When I don’t use the API, and just use straight pl/sql I get 600+- PGA Memory Operation Events.

First we need to build two schemas and one test user. I’m keeping these pretty simple. HR_CODE is to hold business logic. HR_API is to hold the API for the HR schema. The user executes the code in the business logic schema. Here is the basic architecture. NOTE: this is not the full Secure Architecture implantation in this test case. Code Based Access Control and accessible by has not been implemented in this test case. 

create user usr1 identified by x;
create user hr_code identified by x;
create user hr_api identified by x;
--
grant create session to usr1;
grant alter session to usr1;
-- create the required roles.
create role hr_big_table_all;
create role hr_big_table_sel;
-- create a test table in the hr schema
create sequence hr.big_table_seq;
create table hr.big_table (id number not null,
 c number not null,
 d number not null);
-- setup permissions.
grant select on hr.big_table to hr_big_table_sel, hr_api;
grant select, update, insert, delete on hr.big_table to hr_big_table_all;
grant hr_big_table_all to 
grant hr_big_table_sel to usr1;
-- populate the table with 1M rows
insert into hr.big_table (
     select hr.big_table_seq.nextval,
     ceil(sys.dbms_random.value(0,10)),
     sys.dbms_random.value(-1,5000)
     from dual
connect by rownum <= 1000000);
--
commit;
-- add in the constraints and indexes
alter table hr.big_table add constraint
big_table_pk primary key (id);
--
create index hr.big_table_idx1 on hr.big_table(c);
-- analyze the schema
exec sys.dbms_stats.gather_schema_stats(ownname => 'HR');
-- create the decls package. this is used so all schemas can 
-- reference common definitions.
create or replace package hr_decls.big_tab_decl as
 cursor big_tab_cur is
 select id,
         c,
         d
 from hr.big_table;
 subtype st_big_tab is big_tab_cur%rowtype;
 type tt_big_tab is table of st_big_tab index by pls_integer;
end;
/
-- grant execute on the decls package to expose definations
grant execute on hr_decls.big_tab_decl to hr_api, hr_code, usr1;
--
-- create the api packages
create or replace package hr_api.big_tab_api 
authid current_user as
 function sel_hr_big_tab_range(p_low number, p_high number)
 return hr_decls.big_tab_decl.tt_big_tab;
end big_tab_api;
/
-- grant execute on the api to the code schema
grant execute on hr_api.big_tab_api to hr_code;
--create the package body
--
create or replace package body hr_api.big_tab_api 
as

 function sel_hr_big_tab_range(p_low number, p_high number)
 return hr_decls.big_tab_decl.tt_big_tab is
 ltt_big_tab hr_decls.big_tab_decl.tt_big_tab;
 begin
   select id,
          c,
          d
   bulk collect into ltt_big_tab
   from hr.big_table
   where d between p_low and p_high;
 
   return ltt_big_tab;
 exception when no_data_found then
   -- <fixme> insert error handler
   raise_application_error(20000,'sel_hr_big_tab_range');
 end sel_hr_big_tab_range;
end big_tab_api;
/
-- create the code package. this holds the business logic 
-- to create a shell around the API.
-- you will notice, no business logic has been added in 
-- at this point. 
create or replace package hr_code.sel_perf_test
authid definer is
 function sel_hr_big_tab_range(p_low number, p_high number)
 return hr_decls.big_tab_decl.tt_big_tab;
end sel_perf_test;
/
create or replace package body hr_code.sel_perf_test is
 function sel_hr_big_tab_range(p_low number, p_high number) 
 return hr_decls.big_tab_decl.tt_big_tab is
 ltt_big_tab hr_decls.big_tab_decl.tt_big_tab;
 begin
   ltt_big_tab := hr_api.big_tab_api.sel_hr_big_tab_range(p_low => p_low,
                                                     p_high => p_high);
 return ltt_big_tab;
end sel_hr_big_tab_range;
end sel_perf_test;
/
-- grant execute on the business logic code to the user
-- The user only has one way to get to the data, that is 
-- through the business logic.
grant execute on hr_code.sel_perf_test to usr1;
-- test the user executing the business logic code.
conn usr1/x@demo
ALTER session SET timed_statistics=TRUE;
ALTER SESSION SET TRACEFILE_IDENTIFIER = "perf_test_api";
alter session set sql_trace=TRUE;
alter session set events='10046 trace name context forever, level 12';
-- test it.
declare
 ltt_big_tab hr_decls.big_tab_decl.tt_big_tab;
begin
-- ltt_big_tab := hr_code.sel_perf_test.sel_hr_big_tab_pk(p_id => 100);
-- ltt_big_tab := hr_code.sel_perf_test.sel_hr_big_tab_value(p_value => 4);
-- for i in 0 .. 50
-- LOOP
 ltt_big_tab := hr_code.sel_perf_test.sel_hr_big_tab_range(p_low => 50,
 p_high => 900);
-- END LOOP;
end;
/
-- to run the next test, the user will need select on hr.big_table.
conn rlockard@demo
grant hr_big_table_sel to usr1;
conn usr1/x@demo
ALTER session SET timed_statistics=TRUE;
ALTER SESSION SET TRACEFILE_IDENTIFIER = "perf_test_noapi";
alter session set sql_trace=TRUE;
alter session set events='10046 trace name context forever, level 12';
declare
 ltt_big_tab hr_decls.big_tab_decl.tt_big_tab;
begin
  -- for i in 0 .. 50
  -- LOOP
  select id,
         c,
         d
  bulk collect into ltt_big_tab
  from hr.big_table
  where d between 50 and 900;
  -- END LOOP;
end;
/

 

Update to my earlier #quicktip on setting #plsql scope and warnings.

I noticed a error in my code for setting PLSCOPE_SETTINGS and PLSQL_WARNINGS. QuicTip Logon.sql What I did was get the instance name out of v$instance to figure out if I was connecting to a production environment or one of the lower environments. The problem with this is, not everyone is going to have permissions to select on sys.v_$instance. The better way to do this is to use sys_context to get the instance name. This way, you won’t have to chase down additional privileges from your DBA.

<code>

select sys_context('userenv','instance_name')
 into sInst
 from dual;

</code>

Here is the corrected code for my logon.sql

<code>

DECLARE
 sInst varchar2(1);
 BEGIN
 -- rlockard: 2018/02/23 commented out getting instance name from v$instance.
 -- used the more apporiate sys_context('userenv','instance_name')

--select upper(SUBSTR(instance_name, 1,1))
 --INTO sInst
 --FROM SYS.V_$INSTANCE;

select sys_context('userenv','instance_name')
 into sInst
 from dual;

-- test to see if this is a production instance
 -- all production instances start with P so ...
 -- if it's not a production instance set up
 -- session properties approiate for dev / test / sandbox.
 IF sInst != 'P' THEN
 execute immediate 'ALTER SESSION SET PLSCOPE_SETTINGS=' || '''IDENTIFIERS:ALL''';
 execute immediate 'ALTER SESSION SET PLSQL_WARNINGS=' || '''ENABLE:ALL''';
 END IF;
 END;
 /

</code>

My upcoming Spring events @OracleACE #InfoSec

March 5 – 8: I will be speaking at the Hotsos Symposium in Dallas Texas.

https://www.hotsos.com/apex/f?p=200:61801:6152298924404 I will be showing how to secure your high performance code. We will be looking at some coding standards, what common errors we are making that makes our code less secure, and how to implement a trusted path for your data.

March 15: We have managed to wrangle Bobby Curtis, and Steven Feuerstein to come out to Oracle’s Columbia Maryland office to give a couple of presentations. Maybe it’s the Maryland Crab Cakes or could be they are really nice guys. 🙂

https://www.meetup.com/natcapoug-middleware/events/248008692/

Bobby Curtis, Oracle ACE Director Alumni and Product Manager for Oracle Golden Gate will be giving a presentation on Golden Gate Security.

Steven Feuerstein Oracle ACE Director Alumni and Oracle’s Developer Advocate for PL/SQL for Oracle DevGym. This will be a great opportunity for an Oracle DevGym Workout. Devgym.oracle.com Steven will go through the exercises with you and will be giving prizes for the best performers.

March 21 – 22: I will be speaking at Utah Oracle Users Group Training Days (and getting some Spring Skiing in.) http://utoug.org/TrainingDays I will be speaking on Holistic Database Security and Secure Coding. My Holistic Database Security presentation has come a long way over the past ten years. As new attack vectors, mistakes, mitigations come out I update this presentation. So, if you’ve seen this presentation before, don’t worry there is a lot of new material in there. My Secure Coding Presentation goes through coding standards, common errors, and how to implement a trusted path for you data.

April 18: I will be speaking at Twin Cities Oracle Users Group on Oracle Database Vault and a Hybrid Holistic Database Security presentation that will be focused at DBA’s. Many DBA’s fear or don’t like Oracle Database Vault, because it changes the paradigm of how they work. We are accustomed to being the God of our databases. We will be looking at how to make Database Vault your friend, and customizing it for your needs.

May 22 – 23: I will be speaking at Oracle Users Group Finland. http://www.ougf.fi/index.php/en/

Again I will be speaking on Holistic Database Security and Secure Coding.

And the BGOUG Spring Conference. I’m just waiting on the confirmation.

More to come. 🙂