Thread: seeking advices for function

seeking advices for function

From
"Jean-Yves F. Barbier"
Date:
Hi list,

I've got some questions about this function:

=============================
DROP FUNCTION tsttst(TEXT, TEXT, BOOLEAN, INT8, INT8, TEXT[]);
CREATE FUNCTION tsttst(TEXT,         -- FQTN
                       TEXT,         -- Ordering column
                       BOOLEAN,      -- TRUE=ASC / FALSE=DESC
                       INT8,         -- LIMIT
                       INT8,         -- OFFSET
                       TEXT[])       -- Columns' names array
RETURNS SETOF RECORD AS $$
DECLARE
    usr         TEXT;
    ord         TEXT;
    collist     TEXT;
    qry         TEXT;
BEGIN
    -- Retrieve real caller's name
    usr := session_user;
    -- First check for table SELECT privilege
    IF NOT has_table_privilege(usr, $1, 'SELECT') THEN
        -- If needed, check SELECT privilege per column
        FOR i IN 1 .. array_length($6, 1) LOOP
            IF NOT has_column_privilege(usr, $1, $6[i], 'SELECT') THEN
                RAISE EXCEPTION 'FNCT: tsttst: Call forbidden';
                -- ADD HERE LOGING IN TABLE security.alert
                --   YEAH, BUT HOW TO AVOID security.alert NEW ROW BEING
                --   VOIDED FROM A ROLLBACK ???
            END IF;
        END LOOP;
    END IF;
    -- Set ordering direction
    IF $3 THEN
        ord := 'ASC';
    ELSE
        ord := 'DESC';
    END IF;
    -- Construct columns full list
    collist := array_to_string($6, ',');
    -- Build query from input parms
    qry = 'SELECT ' || collist || ' FROM ' || $1 || ' ORDER BY ' || $2 || ' '
          || ord || ' LIMIT ' || $4 || ' OFFSET ' || $5 || ';';
    -- Return the whole query
    RETURN QUERY EXECUTE qry;
END;
$$ LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER;
=============================

* Is it totally non-vulnerable to SQL injection?

* I intend to use this kind of function for data I/O, as a replacement for
  views in an ERP project.
  Considering that overhead introduced by builtin SELECT privilege check is
  far from negligible (from 110ns to 800ns, one row select) but on the other
  hand that it could replace thousands views and that an ERP isn't an
  application that generates hundreds queries per second.
  Is it a good idea or not?

* A big problem is the implementation of trespassing attempts loging (see
  comment in function) which shouldn't be subject to the subsequent rollback;
  how can I do that?

Any constructive critics will be welcome.

JY
--
My doctor told me to stop having intimate dinners for four.  Unless there
are three other people. -- Orson Welles

Re: seeking advices for function

From
Merlin Moncure
Date:
On Wed, Jun 22, 2011 at 2:30 PM, Jean-Yves F. Barbier <12ukwn@gmail.com> wrote:
> Hi list,
>
> I've got some questions about this function:
>
> =============================
> DROP FUNCTION tsttst(TEXT, TEXT, BOOLEAN, INT8, INT8, TEXT[]);
> CREATE FUNCTION tsttst(TEXT,         -- FQTN
>                       TEXT,         -- Ordering column
>                       BOOLEAN,      -- TRUE=ASC / FALSE=DESC
>                       INT8,         -- LIMIT
>                       INT8,         -- OFFSET
>                       TEXT[])       -- Columns' names array
> RETURNS SETOF RECORD AS $$
> DECLARE
>    usr         TEXT;
>    ord         TEXT;
>    collist     TEXT;
>    qry         TEXT;
> BEGIN
>    -- Retrieve real caller's name
>    usr := session_user;
>    -- First check for table SELECT privilege
>    IF NOT has_table_privilege(usr, $1, 'SELECT') THEN
>        -- If needed, check SELECT privilege per column
>        FOR i IN 1 .. array_length($6, 1) LOOP
>            IF NOT has_column_privilege(usr, $1, $6[i], 'SELECT') THEN
>                RAISE EXCEPTION 'FNCT: tsttst: Call forbidden';
>                -- ADD HERE LOGING IN TABLE security.alert
>                --   YEAH, BUT HOW TO AVOID security.alert NEW ROW BEING
>                --   VOIDED FROM A ROLLBACK ???
>            END IF;
>        END LOOP;
>    END IF;
>    -- Set ordering direction
>    IF $3 THEN
>        ord := 'ASC';
>    ELSE
>        ord := 'DESC';
>    END IF;
>    -- Construct columns full list
>    collist := array_to_string($6, ',');
>    -- Build query from input parms
>    qry = 'SELECT ' || collist || ' FROM ' || $1 || ' ORDER BY ' || $2 || ' '
>          || ord || ' LIMIT ' || $4 || ' OFFSET ' || $5 || ';';
>    -- Return the whole query
>    RETURN QUERY EXECUTE qry;
> END;
> $$ LANGUAGE plpgsql STABLE STRICT SECURITY DEFINER;
> =============================
>
> * Is it totally non-vulnerable to SQL injection?
>
> * I intend to use this kind of function for data I/O, as a replacement for
>  views in an ERP project.
>  Considering that overhead introduced by builtin SELECT privilege check is
>  far from negligible (from 110ns to 800ns, one row select) but on the other
>  hand that it could replace thousands views and that an ERP isn't an
>  application that generates hundreds queries per second.
>  Is it a good idea or not?
>
> * A big problem is the implementation of trespassing attempts loging (see
>  comment in function) which shouldn't be subject to the subsequent rollback;
>  how can I do that?
>
> Any constructive critics will be welcome.

I think it's much better to use the database log to record security
violations. Wrapping SQL with a function like this is going to be a
problem factory.  For example, it's a total optimization fence if you
ever need to do something like join against your 'view'.  IMO, it's a
total non-starter.

If you *must* log to a table in a view definition, or want to sneakily
hook custom behaviors to a view generally, you can do something like
this.

say your view is:
CREATE VIEW v as SELECT * FROM foo;

organize your plpgsql function like this:
CREATE FUNCTION priv_check(_view text) RETURNS bool AS
$$
  BEGIN
    IF NOT has_table_privilege(session_user, _view, 'SELECT') THEN
      /* insert into log via dblink (see dblink docs) */
      RAISE ...
    END IF;

    RETURN true;
  END;
$$ LANGUAGE PLPGSQL;

now you can work up your view like this:
CREATE VIEW v as SELECT * FROM foo WHERE (SELECT priv_check('foo'));

I'm pretty sure postgres is going to be smart enough to run priv_check
only once per select from the view in all reasonable cases.  dblink
remains the only way to emit records you want to keep from a
transaction that you want to roll back without recovering.

merlin

Re: seeking advices for function

From
"Jean-Yves F. Barbier"
Date:
On Wed, 22 Jun 2011 15:07:16 -0500, Merlin Moncure <mmoncure@gmail.com> wrote:

...
>
> I think it's much better to use the database log to record security
> violations.

Ok, so I suppose I have to use such a program as pg_fouine (or even just a
script with greps) and email results to the DBA.

> Wrapping SQL with a function like this is going to be a
> problem factory.  For example, it's a total optimization fence if you
> ever need to do something like join against your 'view'.  IMO, it's a
> total non-starter.

This function is to be used against only one table; for joined queries,
I intend to use the same kind of function, however involving all needed tables.

The goal is (if possible) eliminate views because if I've got 200 user
profiles, I'll be obliged to generate 200 x (many)viewS.

> If you *must* log to a table in a view definition, or want to sneakily
> hook custom behaviors to a view generally, you can do something like
> this.
>
> say your view is:
> CREATE VIEW v as SELECT * FROM foo;
>
> organize your plpgsql function like this:
> CREATE FUNCTION priv_check(_view text) RETURNS bool AS
> $$
>   BEGIN
>     IF NOT has_table_privilege(session_user, _view, 'SELECT') THEN
>       /* insert into log via dblink (see dblink docs) */
>       RAISE ...
>     END IF;
>
>     RETURN true;
>   END;
> $$ LANGUAGE PLPGSQL;
>
> now you can work up your view like this:
> CREATE VIEW v as SELECT * FROM foo WHERE (SELECT priv_check('foo'));
>
> I'm pretty sure postgres is going to be smart enough to run priv_check
> only once per select from the view in all reasonable cases.  dblink
> remains the only way to emit records you want to keep from a
> transaction that you want to roll back without recovering.

Ok, I keep that idea in mind :), thanks.

> merlin


--
X-rated movies are all alike ... the only thing they leave to the
imagination is the plot.

Re: seeking advices for function

From
Merlin Moncure
Date:
On Wed, Jun 22, 2011 at 3:45 PM, Jean-Yves F. Barbier <12ukwn@gmail.com> wrote:
> On Wed, 22 Jun 2011 15:07:16 -0500, Merlin Moncure <mmoncure@gmail.com> wrote:
>
> ...
>>
>> I think it's much better to use the database log to record security
>> violations.
>
> Ok, so I suppose I have to use such a program as pg_fouine (or even just a
> script with greps) and email results to the DBA.
>
>> Wrapping SQL with a function like this is going to be a
>> problem factory.  For example, it's a total optimization fence if you
>> ever need to do something like join against your 'view'.  IMO, it's a
>> total non-starter.
>
> This function is to be used against only one table; for joined queries,
> I intend to use the same kind of function, however involving all needed tables.
>
> The goal is (if possible) eliminate views because if I've got 200 user
> profiles, I'll be obliged to generate 200 x (many)viewS.

why in the world do you need to create one view/user/table?  that is
absolutely something you would want to avoid...

merlin

Re: seeking advices for function

From
"Jean-Yves F. Barbier"
Date:
On Wed, 22 Jun 2011 16:27:39 -0500, Merlin Moncure <mmoncure@gmail.com> wrote:

...
>
> why in the world do you need to create one view/user/table?  that is
> absolutely something you would want to avoid...

No, I didn't meant one view/user/table; what I meant is, for example, that
for some tables (that just need to be read alone, w/o joins) I'll juste have
one function instead of Nb tables x view - furthermore, I can't use a view for
some tables as I need to slice answers eg: suppose I've got 15,000 clients, I
can't load the whole list at once; and AFAIK views can't do that.

I also meant using this kind of function (extended of course) to retrieve
joined rows.

But even if I don't have a view per table, my application needs a bunch of
them, which needs to be multiplied by the number of users profiles (eg: buyers
can set purchase price & minimum profit margin up, but salers won't; so,
for this example we already have 2 different views, which also means we must
have 2 different schemas for these categories of users... and so on)

Thing would be *much* easier if a 'SELECT *' returned only the columns on
which user have the SELECT privilege - I guess SQL standard forbid that &| it
is hard to implement.

--
Necessity has no law.
        -- St. Augustine