Mongo bug - How to protect or authenticate my database? - php

How can I protect or authenticate my database in MongoDB?
I created a user using the command line,
use mydb
db.createUser(
{
user: "root",
pwd: "password",
roles: [ { role: "userAdmin", db: "mydb" } ]
}
)
my php,
// connect to mongodb
$m = new MongoClient();
var_dump($m);
// select a database
$db = $m->selectDB("mydb");
var_dump($db);
But why I still can access the database without authentication? What have I missed?
I'm on MongoDB 3.

This is totally expected, not a bug at all.
I am just adding text to Blakes Seven's above link and comment:
This is the guest user concept and does not impact security (see Blakes link: mongodb allows guest connection even when auth is enabled).
This means: your data is safe, guest users can only access meta data. This concept is well known from other services (see Jenkins, see some CMS system etc).

I must run the mongo again
C:\mongodb\bin\mongod.exe --auth
Then the auth works as expected.

Related

Yii2/PHP: Check connection to MySQL and PostgreSQL

When starting my Yii2/PHP application, how can I check if / wait until the database is up?
Currently with MySQL I use:
$time = time();
$ok = false;
do {
try {
$pdo = new PDO($dsn,$username,$password);
if ($pdo->query("SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA"))
$ok=true;
} catch (\Exception $e) {
sleep(1);
}
} while (!$ok && time()<$time+30);
Now I want make my application running with MySQL and PostgreSQL.
But SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA does not work in PostgreSQL.
Is there a SQL-statement (using PDO database connectivity) that works on both database systems to check if the database is up and running?
Yii2 has a property to verify if a connection exists or not, it is really not necessary to create a script for this, since this framework has an abstraction implemented for the databases it supports ($isActive property).
$isActive public read-only property Whether the DB connection is
established
public boolean getIsActive ( )
You can do the check in your default controller in the following way:
<?php
class DefaultController extends Controller
{
public function init()
{
if (!Yii::$app->db->isActive) {
// The connection does not exist.
}
parent::init();
}
}
It is not good practice to force waiting for a connection to a database unless there are very specific requirements. The availability of a connection to the database must be a mandatory requirement for an application to start and the application should not "wait" for the database to be available.
There are ways to run containers in docker in an orderly manner or with a specific requirement, this link could give you a better option instead of delegating this to the application.
You could use SELECT 1 which is standard SQL.
You can use dbfiddle to test against various servers.
The server could go away an any time so checking the error response with every query is a much better approach.

mongo authorization zend shanty

I trying to add authorization to mongoDB.
I use mongodb 2.2.4, php-mongo 1.4.1 stable, apache2, php 5.4, ZendFramework 1.12 + ShantyMongo
Also I use replicaset with 3 instances.
When I set auth = true mongo create admin database, and when I add user to this database I got this error:
Fatal error: Uncaught exception 'MongoCursorException' with message 'test.server.com:27017: unauthorized db:database_test ns:database_test.options lock type:0 client:127.0.0.1' in /apps/test_app/libs/Shanty/Mongo/Collection.php:3501
If I add user to my database_test all work fine.
<?php
/* Configure logging */
MongoLog::setModule( MongoLog::ALL );
MongoLog::setLevel( MongoLog::ALL );
$m = new MongoClient(); // connect
$db = $m->selectDB("database_test");
$db->authenticate('admin','12345');
this code work fine even if I add user to both admin and database_test.
Maybe someone has similar problem and know what I need to do.
First of all, you should use the following syntax for specifying authentication:
<?php
$m = new MongoClient("mongodb://admin:12345#localhost/database_test");
$db = $m->database_test;
It's a bit difficult to answer your question though, as "when I add user to this database" is not really descriptive. In order to get a proper answer, you need to learn to list the exact steps or code that you used to do this.
I am thinking that you were trying to auth against a normal database (database_test), where you would only have a user/pass set for the admin database. In that case, you need to auth to the admin database, and then select your database_test:
<?php
$m = new MongoClient("mongodb://admin:12345#localhost/admin");
$db = $m->database_test;
Thank you for your answer I figured out this.
Maybe it's will be heplful for some one on future, Shanty-Mongo require username, password and port in array of servers.
It's should looks like:
; Shanty Mongo Config
hosts.0.host = '127.0.0.1'
hosts.0.port = '27017'
hosts.0.username = 'root'
hosts.0.password = 'pass'
hosts.1.host = '127.0.0.1'
hosts.1.port = '27018'
hosts.1.username = 'root'
hosts.1.password = 'pass'
hosts.2.host = '127.0.0.1'
hosts.2.port = '27019'
hosts.2.username = 'root'
hosts.2.password = 'pass'
database = 'database_test'
replicaSet = true
Also what I do:
in mongo:
1. edit config file, enable auth (auth = true)
2. create user in admin database
3. connect as new user
4. create user in target database
in ZF config
update config file, as I describe above.
Also some times I get error: Fatal error: php_network_getaddresses, I think it's because I have some trouble with DNS or php trying resolve address: mongo://root:pass#127.0.0.1:27017,root:pass#127.0.0.1:27018,root:pass#127.0.0.1:27019/database_test

Yii database connection issue

Task
What I need is, to authenticate user based on Oracle DB user system:
User enters to login page
Enters username and password
Application tries to establish connection with given parameters
If successful, makes this connection persistent over application
I'm using Yii framework.
What I've done ...
I removed db component from protected/config/main.php file. Based on Larry Ulman's blog post replaced authenticate (which located in protected/components/UserIdentity.php) with following one
public function authenticate()
{
$dsn='oci:dbname=XE;charset=UTF8';
$connection=new CDbConnection($dsn,$this->username,$this->password);
$connection->active=true;
return $connection->getConnectionStatus();
}
Well it didn't establish any connection. BTW in server I tested with oracle client. All works well.
Questions are
I'm not sure if I'm doing it in right manner.
How to make established db connection persistent over whole application?
BTW
My development environment
Oracle DB and Apache 2.2.4 + PHP 5.4 webserver working on Windows Server 2003.
It took some time to understand what you want. So basically it's like:
A user tries to log in with username/password
To authenticate the user, you check, whether you can establish a DB connection using that same username/password as DB credentials
If the connection could be opened, the user should have a db component available, that uses this same connection credentials throughout the user session
Your approach to create a custom UserIdentity is right. They are meant for doing the authentication. I'm not sure, why your code there fails, but you're on the right track. Maybe getConnectionStatus() does not work right for Oracle. You could try to execute another simple SQL command for a test.
If you've managed to test for a successful connection, you should store the username and password in the user session, e.g.
Yii::app()->user->setState('__db_username',$this->username);
Yii::app()->user->setState('__db_password',$this->password);
Now to have the db component available after login is a bit tricky. You can take different approaches. I'll describe two:
1. Create a custom CDbConnection
class DynamicDbConnection extends CDbConnection
{
public function init()
{
$this->username = Yii::app()->user->getState('__db_username');
$this->password = Yii::app()->user->getState('__db_password');
if(!$this->username || !$this->password) {
throw new CException('No username/password available! User not logged in?');
}
parent::init();
}
}
Now you can configure this as db component in your main.php without username and password, of course:
'components' => array(
'db' => array(
'class' => 'DynamicDbConnection',
'connectionString' =>'oci:dbname=XE;charset=UTF8',
),
),
2. Adding a CDbConnection from Controller::init()
You could add a db component from your base controllers init() method:
class Controller extends CController
{
public function init()
{
$username = Yii::app()->user->getState('__db_username');
$password = Yii::app()->user->getState('__db_password');
if($username && $password) {
Yii::app()->setComponent('db', array(
'username' => $username,
'password' => $password,
));
}
parent::init();
}
You also still need a base db component configuration in your main.php for this.
Both approaches may need a little tweaking as i've not tested this. But you should get the basic idea.

Renaming a Mongo Collection in PHP

PHP's Mongo driver lacks a renameCommand function. There is reference to do this through the admin database. But it seems more recent versions of the Mongo driver don't let you just "use" the admin database if do don't have login privileges on that database. So this method no longer works. I've also read this doesn't work in sharded environments although this isn't a concern for me currently.
The other suggestion people seem to have is to iterate through the "from" collection and insert into the "to" collection. With the proper WriteConcern (fire and forget) this could be fairly fast. But it still means pulling down each record over the network into the PHP process and then uploading it back over the network back into the database.
I ideally want a way to do it all server-side. Sort of like an INSERT INTO ... SELECT ... in SQL. This way it is fast, network efficient and a low load on PHP.
I have just tested this, it works as designed ( http://docs.mongodb.org/manual/reference/command/renameCollection/ ):
$mongo->admin->command(array('renameCollection'=>'ns.user','to'=>'ns.e'));
That is how you rename an unsharded collection. One problem with MR is that it will change the shape of the output from the original collection. As such it is not very good at copying a collection. You would be better off copying it manually if your collection is sharded.
As an added note I upgraded to 1.4.2 (which for some reason comes out from the pecl channel into phpinfo() as 1.4.3dev :S) and it still works.
Updates:
Removed my old map/reduce method since I found out (and Sammaye pointed out) that this changes the structure
Made my exec version secondary since I found out how to do it with renameCollection.
I believe I have found a solution. It appears some versions of the PHP driver will auth against the admin database even though it doesn't need to. But there is a workaround where the authSource connection param is used to change this behavior so it doesn't auth against the admin database but instead the database of your choice. So now my renameCollection function is just a wrapper around the renameCollection command again.
The key is to add authSource when connecting. In the below code $_ENV['MONGO_URI'] holds my connection string and default_database_name() returns the name of the database I want to auth against.
$class = 'MongoClient';
if( !class_exists($class) ) $class = 'Mongo';
$db_server = new $class($_ENV['MONGO_URI'].'?authSource='.default_database_name());
Here is my older version that used eval which should also work although some environments don't allow you to eval (MongoLab gives you a crippled setup unless you have a dedicated system). But if you are running in a sharded environment this seems like a reasonable solution.
function renameCollection($old_name, $new_name) {
db()->$new_name->drop();
$copy = "function() {db.$old_name.find().forEach(function(d) {db.$new_name.insert(d)})}";
db()->execute($copy);
db()->$old_name->drop();
}
you can use this. "dropTarget" flag is true then delete exist database.
$mongo = new MongoClient('_MONGODB_HOST_URL_');
$query = array("renameCollection" => "Database.OldName", "to" => "Database.NewName", "dropTarget" => "true");
$mongo->admin->command($query);

Symfony 2 odbc component

Just configuring my Standard Symfony2 edition to be used with the odbc driver. I need that for the Teradata connection.
I completely don't need any ORM tools, I will only use it to return the results from the Teradata Stored Procedure or complex query.
Currently it works in the procedural PHP code by following few simple steps:
- $conn = odbc_connect(HOST, USER, PASSWORD)
- $stmt = odbc_prepare($conn, $query)
- $params = array(1, 2, 3)
- odbc_execute($stmt, $params)
- next just fetch results using odbc_fetch_array($stmt)
Just thinking now about how and where to implement this in the Symfony2 MVC stack according to the Sf2 best practices... Perhaps a separate service?
Will only mention that the ease of use for fresh developers is a priority.
Thanks in advance for any help!
From what you describe I would suggest creating a service which, instead of using global constants receives HOST, USER and PASSWORD via Service Container, i.e. you store that information in your parameters.yml, create a service with these parameters in your bundle's service.yml and finally you create your Service-class which basically just wraps your odbc-calls.
This way you can keep the sensitive data (user, password) out of your git-repo, assuming you are not tracking parameters.yml as is suggested in Symfony's documentation and you can easily access your service from everywhere where you have access to the Service Container like this:
$this->getContainer()->get('acme_demo.teradata_service')
parameters.yml:
parameters:
teradata_host: localhost
teradata_user: username
teradata_password: password
Acme\DemoBundle\Resources\config\services.yml:
services:
acme_demo.teradata_service:
class: Acme\DemoBundle\Service\TeradataService
arguments:
- %teradata_host%
- %teradata_user%
- %teradata_password%
Acme\DemoBundle\Service\TeradataService:
class TeradataService
{
public function __construct($host, $user, $password)
...
}
You could even go further and make the teradata service private in your service.yml and then just create other services which access your TeradataService. This way you can make sure, that deleopers do not interfere with the service directly, but only via ObjectManagers.
Acme\DemoBundle\Resources\config\services.yml:
services:
acme_demo.teradata_service:
public: false
class: Acme\DemoBundle\Service\TeradataService
arguments:
- %teradata_host%
- %teradata_user%
- %teradata_password%
acme_demo.another_service:
class: Acme\DemoBundle\Service\ServiceAccessingTeradata
arguments:
- #acme_demo.teradata_service
This way your services are not bound to your teradata-connection directly, which makes it easier to replace Acme\DemoBundle\Service\TeradataService with something else.

Categories